/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 80028 (8.0.28)
 Source Host           : localhost:3306
 Source Schema         : my_blog

 Target Server Type    : MySQL
 Target Server Version : 80028 (8.0.28)
 File Encoding         : 65001

 Date: 09/12/2022 12:51:50
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for tb_blog
-- ----------------------------
DROP TABLE IF EXISTS `tb_blog`;
CREATE TABLE `tb_blog` (
  `blog_id` bigint NOT NULL COMMENT '博客id',
  `user_id` bigint NOT NULL COMMENT '外键,发表用户ID',
  `blog_cover` varchar(255) DEFAULT NULL COMMENT '博客封面',
  `blog_title` text NOT NULL COMMENT '博客标题',
  `blog_description` varchar(256) DEFAULT '未添加相关描述的博客' COMMENT '博客描述',
  `blog_content` longtext NOT NULL COMMENT '博客内容',
  `blog_views` bigint DEFAULT '0' COMMENT '浏览量',
  `blog_comment_count` bigint DEFAULT '0' COMMENT '评论量',
  `blog_like_count` bigint DEFAULT '0' COMMENT '点赞数',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `version` int NOT NULL DEFAULT '0' COMMENT '版本号',
  `deleted` int DEFAULT '0' COMMENT '逻辑删除:0未删除;1删除',
  PRIMARY KEY (`blog_id`),
  KEY `user_id` (`user_id`),
  CONSTRAINT `fk_user_id` FOREIGN KEY (`user_id`) REFERENCES `tb_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='博客表';

-- ----------------------------
-- Records of tb_blog
-- ----------------------------
BEGIN;
INSERT INTO `tb_blog` (`blog_id`, `user_id`, `blog_cover`, `blog_title`, `blog_description`, `blog_content`, `blog_views`, `blog_comment_count`, `blog_like_count`, `create_time`, `update_time`, `version`, `deleted`) VALUES (1, 8888888888888888888, '../../images/blog/post-1.png', 'Redis', '关于redis的学习笔记', '# 基础篇\n## Redis简介\nREmote DIctionary Server(Redis) 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统，是跨平台的非关系型数据库。\nRedis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库，并提供多种语言的 API。\nRedis 通常被称为数据结构服务器，因为值（value）可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。\n## Redis介绍\nRedis 是完全开源的，遵守 BSD 协议，是一个高性能的 key-value 数据库。\nRedis 与其他 key - value 缓存产品有以下三个特点：\n\n- Redis支持数据的持久化，可以将内存中的数据保存在磁盘中，重启的时候可以再次加载进行使用。\n- Redis不仅仅支持简单的key-value类型的数据，同时还提供list，set，zset，hash等数据结构的存储。\n- Redis支持数据的备份，即master-slave模式的数据备份。\n## Redis 优势\n\n- 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。\n- 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。\n- 原子 – Redis的所有操作都是原子性的，意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务，即原子性，通过MULTI和EXEC指令包起来。\n- 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。\n## 安装Redis(Mac版)\n使用Homebrew安装命令\n```shell\nbrew install redis\n```\n安装成功后Redis文件目录在`/opt/homebrew/Cellar`中\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661409824838-4202674e-62aa-4e58-9bc4-940098d07c4a.png#clientId=u6603ef56-9a15-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=464&id=u7cf4e1c3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=928&originWidth=1840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=325199&status=error&style=shadow&taskId=uf951dda7-8f3b-433c-bef6-dd296ea9d12&title=&width=920)\n配置PATH变量\n\n```shell\n# redis\nexport REDIS_HOME=/opt/homebrew/Cellar/redis/7.0.0 \nexport PATH=$PATH:$REDIS_HOME/bin\n```\n执行`source ~/.bash_profile`命令\n## 启动Redis\n### 前台启动\n输入`redis-server`运行redis服务； 默认端口号`6379`； `control+c`停止服务\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661410105387-557a6104-c02a-4b1d-a49f-3a5cad2d1a03.png#clientId=u6603ef56-9a15-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=542&id=uf462353a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1084&originWidth=1928&originalType=binary&ratio=1&rotation=0&showTitle=false&size=159528&status=error&style=shadow&taskId=u95fb0118-e037-43b1-9478-1458cac2753&title=&width=964)\n创建一个新的终端窗口输入`redis-cli`连接redis(注意保持redis的运行)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661410274524-36127dc8-ef90-4143-8655-ba5a2b1e652e.png#clientId=u6603ef56-9a15-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=542&id=u5f9dfe93&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1084&originWidth=1928&originalType=binary&ratio=1&rotation=0&showTitle=false&size=57671&status=error&style=shadow&taskId=u2565c1b0-5ba4-4103-b4d3-09e6d170289&title=&width=964)\n### 指定配置启动\n如果要让Redis后台启动，必须修改Redis的配置文件\n使用homebrew安装的redis的配置文件在`/opt/homebrew/etc`目录下的`redis.conf`\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661411077496-d12f4b89-aee2-4672-8b55-3a89bb8c69c4.png#clientId=u6603ef56-9a15-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=464&id=u889ff903&margin=%5Bobject%20Object%5D&name=image.png&originHeight=928&originWidth=1810&originalType=binary&ratio=1&rotation=0&showTitle=false&size=214145&status=error&style=shadow&taskId=ueb51136a-7b90-447e-be5c-eae68c710d2&title=&width=905)\n我们先备份一份配置文件\n```shell\ncp redis.conf redis.conf.bck\n```\n然后修改配置文件`vim redis.config`\n修改以下配置\n```properties\n# 监听的地址 默认是127.0.0.1 会导致只能在本地访问，修改为0.0.0.0则可以在任意IP访问，生产环境不要设置为0.0.0.0\nbind 127.0.0.1\n# 守护进程 修改为yes后可以后台启动\ndaemonize yes\n# 密码 设置后 访问Redis必须输入密码\nrequirepass ${yourPass}\n```\nRedis中其他常见的配置\n```properties\n# 端口号\nport 6379\n# 工作目录，默认是当前目录 也就是运行redis-server时候的命令，日志，持久化等文件会保存在这个目录\ndir .\n# 数据库数量 设置为1 ，代表只使用1个库 默认有16个库 标号0-15\ndatabase 1\n# 设置redis能够使用的最大内存\nmaxmemory 512mb\n# 日志文件，默认为空 不记录日志，可以指定日志文件名称\nlogfile \"redis.log\"\n```\n启动Redis\n```shell\n# 进入redis的配置文件目录 /opt/homebrew/etc\ncd /opt/homebrew/etc\n# 启动\nredis-server redis.config\n# 如果不在这个目录下则使用下面的命令\nredis-server /usr/local/etc/redis.conf\n```\n停止redis\n```shell\n# 查看redis的进程信息\nps -ef | grep redis\n#结果:501 82848     1   0  3:28下午 ??         0:00.14 redis-server 127.0.0.1:6379\n# 杀死进程\nkill -9 82848\n```\n### 开机自启\n```shell\n# 加软连接，加入launchd进程，当用户登陆系统后会被执行\nln -f /opt/homebrew/Cellar/redis/7.0.0/homebrew.mxcl.redis.plist ~/Library/LaunchAgents/\n# 加载任务\nlaunchctl load ~/Library/LaunchAgents/homebrew.mxcl.redis.plist\n```\n使用brew启动redis服务\n```shell\nbrew services start redis\n```\n关闭redis服务\n```shell\nbrew services stop redis\n```\n重启\n```shell\nbrew services restart redis\n```\n至此已经完成了Redis的安装、启动、连接\n接下来就可以进行愉快的使用了\n## 安装图形化界面\n### RDM\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661416010484-86686277-3afa-418f-90fc-c113912bbe25.png#clientId=u6603ef56-9a15-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=828&id=u09b8c543&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1656&originWidth=2360&originalType=binary&ratio=1&rotation=0&showTitle=false&size=174652&status=error&style=shadow&taskId=u1ddcff9e-c317-4680-a9fb-2724d95fd72&title=&width=1180)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661416091240-dcb77875-935f-413f-8c62-8057ca8f4a1e.png#clientId=u6603ef56-9a15-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=828&id=u3d0028e4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1656&originWidth=2360&originalType=binary&ratio=1&rotation=0&showTitle=false&size=152410&status=error&style=shadow&taskId=u1be7f57e-8111-4c72-b077-1586f58bf42&title=&width=1180)\n## Redis常见命令\n### Redis数据结构介绍\nRedis是一个key-value的数据库，key一般是String类型的，**不过value的类型是多种多样的**\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661416472391-e86f11a1-2b18-4620-9d4e-6ae1f75888cf.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=443&id=ud037eea5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=886&originWidth=1818&originalType=binary&ratio=1&rotation=0&showTitle=false&size=344009&status=error&style=shadow&taskId=u43cf1667-3b00-42dc-a9e9-aa6bc54284d&title=&width=909)\n官网查看命令[https://redis.io/commands/](https://redis.io/commands/)\n### Redis的通用命令\n通用命令是部分数据类型的，都可以使用的指令，常见的有：\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661417346681-1400eab3-7e00-4aff-92e3-d1b93f84a705.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=438&id=u20eb94fc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=876&originWidth=1654&originalType=binary&ratio=1&rotation=0&showTitle=false&size=469003&status=error&style=shadow&taskId=ub6355316-2979-4ee6-b31b-a1a65f71922&title=&width=827)\n### String类型\nString类型，也就是字符串类型，是Redis中最简单的存储类型。\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661417523807-0a3636c9-20f8-42db-8226-1c5b30383c82.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=395&id=u13746cda&margin=%5Bobject%20Object%5D&name=image.png&originHeight=790&originWidth=2018&originalType=binary&ratio=1&rotation=0&showTitle=false&size=527045&status=error&style=shadow&taskId=ufe9eb64a-988a-4ae6-82fb-32df6d603f1&title=&width=1009)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661418191575-0d1f0c7c-608d-43dc-bf00-158351cbbb21.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=395&id=u4319c3b4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=790&originWidth=1684&originalType=binary&ratio=1&rotation=0&showTitle=false&size=409898&status=error&style=shadow&taskId=uf260ea28-e04d-4917-ba5e-02faf5cb4f7&title=&width=842)\n### key的层级格式\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661418380921-23965312-2772-42a6-a26e-fc81e212e374.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=525&id=uc794c515&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1050&originWidth=2204&originalType=binary&ratio=1&rotation=0&showTitle=false&size=552271&status=error&style=shadow&taskId=u6d20aef3-d084-43f9-89ab-0166e16045d&title=&width=1102)\n```shell\n127.0.0.1:6379> set app:user:1 \'{\"id\":1, \"name\":\"zs\", age:10}\'\nOK\n127.0.0.1:6379> set app:user:2 \'{\"id\":2, \"name\":\"ls\", age:12}\'\nOK\n127.0.0.1:6379> set app:product:1 \'{\"id\":1 \"name\":\"手机\", price:2999}\'\nOK\n127.0.0.1:6379> set app:product:2 \'{\"id\":2 \"name\":\"手机\", price:2399}\'\nOK\n```\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661419060336-c9dbf47b-d307-4d7d-86d7-24b261bdc619.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=314&id=u4ebecfed&margin=%5Bobject%20Object%5D&name=image.png&originHeight=628&originWidth=802&originalType=binary&ratio=1&rotation=0&showTitle=false&size=73957&status=error&style=shadow&taskId=u4b1f0794-8df5-4793-a7c5-b41e519101c&title=&width=401)\n### Hash类型\nHash类型，也叫散列，其value是一个无序字典，类似于java中的HashMap结构(存在差异)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661419257372-15af72ef-7c9a-4254-b362-69384e501bbb.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=466&id=u47c5d905&margin=%5Bobject%20Object%5D&name=image.png&originHeight=932&originWidth=1662&originalType=binary&ratio=1&rotation=0&showTitle=false&size=324262&status=error&style=shadow&taskId=uea25a493-db92-46f6-896e-f63871eec1b&title=&width=831)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661419383970-2b4e517a-b679-4f5a-9a49-277ffcbd3ce8.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=420&id=u3519ebd2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=840&originWidth=1668&originalType=binary&ratio=1&rotation=0&showTitle=false&size=439650&status=error&style=shadow&taskId=u0883c3bb-eb97-4121-9dc4-1860a674b99&title=&width=834)\n```shell\n127.0.0.1:6379> hset app:user:3 name 张三\n(integer) 1\n127.0.0.1:6379> hset app:user:3 age 10\n(integer) 1\n```\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661419558889-ebaf0bb4-e112-4ad0-b4c5-d16b47a379e4.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=828&id=uc9d36099&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1656&originWidth=2360&originalType=binary&ratio=1&rotation=0&showTitle=false&size=480923&status=error&style=shadow&taskId=u83eeabe2-bcca-4033-8c09-3eea510f331&title=&width=1180)\n### List类型\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661420004419-c9c8475b-91e1-4fdb-bbc2-a8ce2964d64a.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=342&id=u8f972e63&margin=%5Bobject%20Object%5D&name=image.png&originHeight=684&originWidth=2150&originalType=binary&ratio=1&rotation=0&showTitle=false&size=244061&status=error&style=shadow&taskId=u2bea6e25-357b-49cb-a623-b64053fbc20&title=&width=1075)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661420199942-85e39d06-405b-4d20-a4a1-6532704589c6.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=360&id=u300f60b0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=720&originWidth=2100&originalType=binary&ratio=1&rotation=0&showTitle=false&size=585254&status=error&style=shadow&taskId=u6d52c231-da6d-4767-a931-713adf3c059&title=&width=1050)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661420567564-aef94c4c-19dc-4d2f-b570-2adcd956c0d9.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=448&id=u519d9f1a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=896&originWidth=2432&originalType=binary&ratio=1&rotation=0&showTitle=false&size=461756&status=error&style=shadow&taskId=u1b5c73b3-195d-4e4e-b275-fd368456157&title=&width=1216)\n`BLPOP`和`BRPOP`: 演示\n![Screen-2022-08-25-175146(1).gif](https://cdn.nlark.com/yuque/0/2022/gif/26314652/1661421618624-795f6e88-6ed6-4374-a138-f4fdeadd2aa4.gif#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=540&id=u2024e7aa&margin=%5Bobject%20Object%5D&name=Screen-2022-08-25-175146%281%29.gif&originHeight=1080&originWidth=1938&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5921596&status=error&style=shadow&taskId=u4b4f5343-239b-4e7d-98c0-56453e1685a&title=&width=969)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661422190396-e77ef5d5-88c3-4e54-a57a-bfc39c11be5d.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=290&id=u006681ec&margin=%5Bobject%20Object%5D&name=image.png&originHeight=580&originWidth=742&originalType=binary&ratio=1&rotation=0&showTitle=false&size=186394&status=error&style=shadow&taskId=u6ca123bc-96ec-467d-8c72-dd483610c7b&title=&width=371)\n### set类型\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661423030175-a0344016-cd8c-4f41-96ad-29036d1aef2f.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=193&id=u613346cc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=386&originWidth=1678&originalType=binary&ratio=1&rotation=0&showTitle=false&size=119457&status=error&style=shadow&taskId=u495e2668-f735-4ff1-b5ff-92a8be1a563&title=&width=839)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661423281472-ed651e88-1c98-4204-aa9c-642e207d99c2.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=292&id=uf75acb20&margin=%5Bobject%20Object%5D&name=image.png&originHeight=584&originWidth=1678&originalType=binary&ratio=1&rotation=0&showTitle=false&size=538756&status=error&style=shadow&taskId=u47421cbf-f464-4fe3-a7bb-24653e351ff&title=&width=839)\n```shell\n127.0.0.1:6379> sadd s1 a b c # 向s1中添加 a b c 三个元素\n(integer) 3\n127.0.0.1:6379> SREM s1 a # 删除s1中的a\n(integer) 1\n127.0.0.1:6379> SCARD s1 # 计算s1中元素个数\n(integer) 2\n127.0.0.1:6379> SISMEMBER s1 b # 判断s1中有没有 b 这个元素\n(integer) 1\n127.0.0.1:6379> SMEMBERS s1 # 获取s1中所有的元素\n1) \"b\"\n2) \"c\"\n127.0.0.1:6379> sadd s2 b c d\n(integer) 3\n127.0.0.1:6379> sadd s1 a b c\n(integer) 1\n127.0.0.1:6379> SMEMBERS s1\n1) \"b\"\n2) \"c\"\n3) \"a\"\n127.0.0.1:6379> SMEMBERS s2\n1) \"b\"\n2) \"d\"\n3) \"c\"\n127.0.0.1:6379> SINTER s1 s2 # s1 和 s2 两个集合的 交集\n1) \"b\"\n2) \"c\"\n127.0.0.1:6379> SDIFF s1 s2 # s1 与 s2 的差集\n1) \"a\"\n127.0.0.1:6379> SUNION s1 s2 # s1 与 s2 的并集\n1) \"b\"\n2) \"d\"\n3) \"c\"\n4) \"a\"\n```\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661423735923-cb3ec425-e08d-4fcb-9845-c3d6a8e055d6.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=427&id=uea673c86&margin=%5Bobject%20Object%5D&name=image.png&originHeight=854&originWidth=678&originalType=binary&ratio=1&rotation=0&showTitle=false&size=258776&status=error&style=shadow&taskId=u0e7b599b-72b3-48fa-b1ea-12973ac981c&title=&width=339)\n```shell\n127.0.0.1:6379> sadd zs ls ww zl # 向zs中添加ls ww zl\n(integer) 3\n127.0.0.1:6379> sadd ls ww mz eg # 向ls中添加 ww mz eg\n(integer) 3\n127.0.0.1:6379> SCARD zs # 查询 zs 好友数量\n(integer) 3\n127.0.0.1:6379> SINTER zs ls # 查询 zs 和 ls 的共同好友\n1) \"ww\"\n127.0.0.1:6379> SDIFF zs ls # 查询 zs 与 ls 的差集\n1) \"zl\"\n2) \"ls\"\n127.0.0.1:6379> SUNION zs ls # 查询 zs 和 ls 的并集\n1) \"zl\"\n2) \"ww\"\n3) \"ls\"\n4) \"mz\"\n5) \"eg\"\n127.0.0.1:6379> SISMEMBER zs ls # 查询 zs 好友是否有ls\n(integer) 1\n127.0.0.1:6379> SISMEMBER ls zs # 查询 ls 好友是否有zs\n(integer) 0\n127.0.0.1:6379> SREM zs ls # zs删除ls\n(integer) 1\n```\n### SortedSet类型\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661425769965-9c849c99-ff84-4cca-831c-0e095526d8ed.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=310&id=u86617623&margin=%5Bobject%20Object%5D&name=image.png&originHeight=620&originWidth=2182&originalType=binary&ratio=1&rotation=0&showTitle=false&size=298520&status=error&style=shadow&taskId=u0898fdef-f854-4c10-8d7e-a46b8632638&title=&width=1091)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661426517513-bf0aca8f-37d5-4797-9ab9-9d604cb8c268.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=496&id=ue27788f1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=992&originWidth=1828&originalType=binary&ratio=1&rotation=0&showTitle=false&size=626225&status=error&style=shadow&taskId=u90db8ec1-3254-4d2b-b81a-f1a24f4c68a&title=&width=914)\n\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661482520614-7286019c-6d08-47c8-ac48-bce4b565237a.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=429&id=u86e7002f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=858&originWidth=1230&originalType=binary&ratio=1&rotation=0&showTitle=false&size=256247&status=error&style=shadow&taskId=u241f37fa-f43b-4afe-b6f3-ab79a155d50&title=&width=615)\n```shell\n127.0.0.1:6379> ZADD stus 85 jack 89 lucy 82 rose 78 jerry 92 amy 95 tom 76 miles\n(integer) 7\n127.0.0.1:6379> ZREM stus tom\n(integer) 1\n127.0.0.1:6379> ZSCORE stus amy\n\"92\"\n127.0.0.1:6379> ZRANK stus rose\n(integer) 2\n127.0.0.1:6379> ZREVRANK stus rose\n(integer) 3\n127.0.0.1:6379> ZCARD stus\n(integer) 6\n127.0.0.1:6379> ZCOUNT stus 0 80\n(integer) 2\n127.0.0.1:6379> ZINCRBY stus 2 amy\n\"94\"\n127.0.0.1:6379> ZREVRANGE stus 0 2\n1) \"amy\"\n2) \"lucy\"\n3) \"jack\"\n127.0.0.1:6379> ZRANGEBYSCORE stus 0 80\n1) \"miles\"\n2) \"jerry\"\n```\n## Redis的Java客户端\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661502039256-6390d6e0-756c-4835-8d63-65fd8ce80ba6.png#clientId=u7de7a9c9-8435-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=482&id=u20f10130&margin=%5Bobject%20Object%5D&name=image.png&originHeight=964&originWidth=2084&originalType=binary&ratio=1&rotation=0&showTitle=false&size=793515&status=error&style=shadow&taskId=u6313d93f-2249-48f5-8293-c5b383e3107&title=&width=1042)\n### Jedis\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661502645675-0377523a-629f-40a1-8564-f62de97602e1.png#clientId=uc018dac3-8674-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=503&id=u0b05cadf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1006&originWidth=1764&originalType=binary&ratio=1&rotation=0&showTitle=false&size=481908&status=error&style=shadow&taskId=u979bd577-c891-4dbe-a540-bb4b9f368a4&title=&width=882)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661502793669-ab480f6c-aa75-4c29-b13a-79a9cd7dad10.png#clientId=uc018dac3-8674-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=467&id=u4fa50d40&margin=%5Bobject%20Object%5D&name=image.png&originHeight=934&originWidth=1842&originalType=binary&ratio=1&rotation=0&showTitle=false&size=411718&status=error&style=shadow&taskId=u8205873b-f226-44c5-8901-8011ea5fdb8&title=&width=921)\n#### Jedis连接池\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661503744173-98a91d50-1765-43d2-a743-acb8d0f7deff.png#clientId=u5d9e8519-eb1b-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=486&id=u3f9422bd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=972&originWidth=2226&originalType=binary&ratio=1&rotation=0&showTitle=false&size=679348&status=error&style=shadow&taskId=u92dfb17e-881c-41fb-9fa6-5a3336c712b&title=&width=1113)\n```java\npackage com.red.jedis.util;\n\nimport redis.clients.jedis.Jedis;\nimport redis.clients.jedis.JedisPool;\nimport redis.clients.jedis.JedisPoolConfig;\n\npublic class JedisConnectionFactory {\n    private static final JedisPool jedisPool;\n    static {\n        //配置连接池\n        JedisPoolConfig config = new JedisPoolConfig();\n        //最大连接池数量\n        config.setMaxTotal(8);\n        //空闲最大连接池数量\n        config.setMaxIdle(8);\n        //空闲最小连接\n        config.setMinIdle(0);\n        //当没有连接池是否要等待,等待多长时间,默认值-1无限等待\n        config.setMaxWaitMillis(1000);//最多等待1秒\n\n        jedisPool = new JedisPool(config,\"localhost\",6379,1000);\n    }\n    public static Jedis getJedis(){\n        return jedisPool.getResource();\n    }\n}\n\n```\n### SpringDataRedis\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661504483065-ef22b2cb-dbc7-4a90-b7e8-06385a80f3fc.png#clientId=u5d9e8519-eb1b-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=474&id=ufb416a54&margin=%5Bobject%20Object%5D&name=image.png&originHeight=948&originWidth=2226&originalType=binary&ratio=1&rotation=0&showTitle=false&size=549156&status=error&style=shadow&taskId=u408c4a0c-1eee-4445-ba0a-4387c592f05&title=&width=1113)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661505080640-bd46ef4f-2fa6-40ea-9afa-e9ce8daa73b2.png#clientId=u5d9e8519-eb1b-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=443&id=u618cd7e6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=886&originWidth=2174&originalType=binary&ratio=1&rotation=0&showTitle=false&size=528177&status=error&style=shadow&taskId=u30107655-fb98-4e82-99c4-f2f9f3da39b&title=&width=1087)\n#### SpringDataRedis快速入门\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661505279462-f60a2946-09e5-4c77-882b-39bf66ed035b.png#clientId=u5d9e8519-eb1b-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=669&id=u543f888a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1338&originWidth=1412&originalType=binary&ratio=1&rotation=0&showTitle=false&size=235686&status=error&style=shadow&taskId=uf178dc8a-7a47-4166-9fb3-971df30aa4f&title=&width=706)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661505412557-96b5d36c-5ac6-42f7-affb-337e09bb8ebe.png#clientId=u5d9e8519-eb1b-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=445&id=u3e3d4032&margin=%5Bobject%20Object%5D&name=image.png&originHeight=890&originWidth=1978&originalType=binary&ratio=1&rotation=0&showTitle=false&size=409093&status=error&style=shadow&taskId=u8bca3221-a0d3-4f71-b6f3-aad7591cc63&title=&width=989)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661505428513-33248609-66fe-40c1-ac66-bffd4af31752.png#clientId=u5d9e8519-eb1b-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=449&id=ua53f380f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=898&originWidth=1902&originalType=binary&ratio=1&rotation=0&showTitle=false&size=254118&status=error&style=shadow&taskId=uf9ea72bc-3588-4252-8fe6-3f4a887b2a2&title=&width=951)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661505509773-372681cf-36bb-4409-b310-27dd186ec18a.png#clientId=u5d9e8519-eb1b-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=505&id=u1f03664b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1010&originWidth=1866&originalType=binary&ratio=1&rotation=0&showTitle=false&size=736968&status=error&style=shadow&taskId=u67a08d86-8fe3-4ea3-9e24-d819402c306&title=&width=933)\n```xml\n<dependencies>\n\n<!--        redis-->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-data-redis</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n            <optional>true</optional>\n        </dependency>\n<!--        jedis-->\n        <dependency>\n            <groupId>redis.clients</groupId>\n            <artifactId>jedis</artifactId>\n        </dependency>\n<!--        连接池-->\n        <dependency>\n            <groupId>org.apache.commons</groupId>\n            <artifactId>commons-pool2</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n</dependencies>\n```\n```yaml\nspring:\n  redis:\n    database: 0 # 选择库\n    host: localhost\n    port: 6379\n    client-type: lettuce # 使用lettuce客户端连接\n#    lettuce连接配置\n    lettuce:\n      pool:\n        max-active: 8\n        max-idle: 8\n        max-wait: 1000ms\n        min-idle: 0\n#    jedis连接配置\n    jedis:\n      pool:\n        max-active: 8\n        max-idle: 8\n        max-wait: 1000ms\n        min-idle: 0\n\n```\n```java\npackage com.red;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.core.ValueOperations;\n\nimport javax.annotation.Resource;\n\n@SpringBootTest\nclass SpringDataRedisDemoApplicationTests {\n\n    @Resource\n    private RedisTemplate redisTemplate;\n\n    @Test\n    void testString() {\n        ValueOperations ops = redisTemplate.opsForValue();\n        ops.set(\"name\",\"虎哥\");\n        Object name = ops.get(\"name\");\n        System.out.println(name);\n    }\n\n}\n\n```\n#### SpringDataRedis的序列化方式\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661506847229-34c0e564-8bdb-453c-b7c3-bb8b22211ced.png#clientId=uf2c063df-c211-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=485&id=u8d398692&margin=%5Bobject%20Object%5D&name=image.png&originHeight=970&originWidth=2240&originalType=binary&ratio=1&rotation=0&showTitle=false&size=502718&status=error&style=shadow&taskId=u1fd3e375-e88f-4db4-b73b-e33728d1a22&title=&width=1120)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661506974923-8db3f91b-3ea7-4aa8-b807-9056b3568d9b.png#clientId=uf2c063df-c211-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=470&id=ue62fd386&margin=%5Bobject%20Object%5D&name=image.png&originHeight=940&originWidth=2192&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1010059&status=error&style=shadow&taskId=u8af864b4-507a-40f1-a44b-4bd61b51f19&title=&width=1096)\n```java\npackage com.red.config;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;\nimport org.springframework.data.redis.serializer.RedisSerializer;\n\nimport javax.annotation.Resource;\n\n@Configuration\npublic class RedisConfig {\n    @Bean\n    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){\n        //创建RedisTemplate对象\n        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();\n        //设置连接工厂\n        redisTemplate.setConnectionFactory(connectionFactory);\n        //创建JSON序列化工具\n        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();\n        //设置key的序列化\n        redisTemplate.setKeySerializer(RedisSerializer.string());\n        redisTemplate.setHashKeySerializer(RedisSerializer.string());\n        //设置Value序列化\n        redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);\n        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);\n\n        return redisTemplate;\n    }\n}\n\n```\n```java\npackage com.red;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.core.ValueOperations;\n\n@SpringBootTest\nclass SpringDataRedisDemoApplicationTests {\n\n    @Autowired\n    private RedisTemplate<String, Object> redisTemplate;\n\n    @Test\n    void testString() {\n        ValueOperations<String, Object> ops = redisTemplate.opsForValue();\n        ops.set(\"name\",\"虎哥\");\n        Object name = ops.get(\"name\");\n        System.out.println(name);\n    }\n\n}\n\n```\n#### StringRedisTemplat\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661507886914-6286d6e8-842d-4fca-8d2d-36736c92323c.png#clientId=u2cb75d07-6287-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=410&id=u24062105&margin=%5Bobject%20Object%5D&name=image.png&originHeight=820&originWidth=2184&originalType=binary&ratio=1&rotation=0&showTitle=false&size=330515&status=error&style=shadow&taskId=uf7ea8263-6421-4f3f-a091-a00910bf4f9&title=&width=1092)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661508019421-3086bd6c-906d-4f1f-81e0-f165334ff11f.png#clientId=u2cb75d07-6287-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=482&id=ud6863837&margin=%5Bobject%20Object%5D&name=image.png&originHeight=964&originWidth=2314&originalType=binary&ratio=1&rotation=0&showTitle=false&size=592766&status=error&style=shadow&taskId=u863c5306-363a-4351-b13e-95ab471475c&title=&width=1157)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661508102482-85230666-1699-4bc6-bbc6-efcbc4fcf97e.png#clientId=u2cb75d07-6287-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=492&id=u90d1beef&margin=%5Bobject%20Object%5D&name=image.png&originHeight=984&originWidth=2210&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1110660&status=error&style=shadow&taskId=u356bcde8-06a2-48ee-9aec-b4a15081672&title=&width=1105)\n```java\npackage com.red;\n\nimport com.alibaba.fastjson.JSON;\nimport com.red.pojo.User;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.data.redis.core.ValueOperations;\n\n@SpringBootTest\nclass RedisStringTests {\n\n    @Autowired\n    private StringRedisTemplate stringRedisTemplate;\n\n    @Test\n    void testString() {\n        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();\n        ops.set(\"name\",\"虎哥\");\n        Object name = ops.get(\"name\");\n        System.out.println(name);\n    }\n\n    @Test\n    void testObject() {\n        //创建对象\n        User user = new User(\"虎哥\", 20);\n        //手动序列化为JSON 引入fastjson\n        String jsonString = JSON.toJSONString(user);\n        //存数据\n        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();\n        ops.set(\"user:100\",jsonString);\n        //取数据\n        String jsonUser = ops.get(\"user:100\");\n        User user1 = JSON.parseObject(jsonUser, User.class);\n        System.out.println(\"user1 = \" + user1);\n\n\n    }\n\n\n\n}\n\n```\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661508694031-7726d838-6cb4-48b1-8869-f3c0c82f2329.png#clientId=u2cb75d07-6287-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=451&id=u6345deae&margin=%5Bobject%20Object%5D&name=image.png&originHeight=902&originWidth=1414&originalType=binary&ratio=1&rotation=0&showTitle=false&size=233349&status=error&style=shadow&taskId=u1e0640cf-ff99-44ec-8b99-c18ddad9868&title=&width=707)\n#### 操作Hash类型\n```java\n@Test\n    void testHash(){\n        HashOperations<String, Object, Object> ops = stringRedisTemplate.opsForHash();\n        ops.put(\"user:400\",\"name\",\"胖虎\");\n        ops.put(\"user:400\",\"age\",\"21\");\n\n        Map<Object, Object> entries = ops.entries(\"user:400\");\n        System.out.println(\"entries = \" + entries);\n    }\n```\n# 实战篇\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661558498696-24f07e7c-f8c0-4577-9164-cf63176627ad.png#clientId=u0f60a675-c98a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=545&id=u307d4058&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1090&originWidth=2198&originalType=binary&ratio=1&rotation=0&showTitle=false&size=933864&status=error&style=shadow&taskId=u46d7830c-80e0-418b-bfaf-decc8b29139&title=&width=1099)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661558565097-2a05f37f-b01c-4090-a381-db89716226f3.png#clientId=u0f60a675-c98a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=441&id=u79a529a5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=882&originWidth=460&originalType=binary&ratio=1&rotation=0&showTitle=false&size=223275&status=error&style=shadow&taskId=u53caf332-1516-4d0e-a8fc-52432bf34f9&title=&width=230)\n## 短信登陆\n### 导入项目\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661559993011-695981aa-a720-4cae-b620-e07219e6410f.png#clientId=u0f60a675-c98a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=478&id=u426b2ec1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=956&originWidth=2230&originalType=binary&ratio=1&rotation=0&showTitle=false&size=273656&status=error&style=shadow&taskId=u1759fb3c-e8e3-42a4-83bf-25bbe6b674d&title=&width=1115)\n#### 后端\n导入资料，注意修改mysql以及redis的配置，修改完毕即可运行\n\n#### 前端(使用Nginx)\n##### 安装(Mac版)\n推荐使用**homebrew**安装\n```shell\n# 搜索Nginx\nbrew search nginx\n# 安装\nbrew install nginx\n# 查看信息\nbrew info nginx\n\n```\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661567024311-a96adc43-49dd-41b2-990f-8b0629f9ecd5.png#clientId=u0f60a675-c98a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=622&id=u27f6268e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1244&originWidth=1784&originalType=binary&ratio=1&rotation=0&showTitle=false&size=270480&status=error&style=shadow&taskId=u613db6ba-5c66-4306-a33b-38291b17fff&title=&width=892)\n把前端打包文件放入www\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661567066199-087440b6-ca87-4c1d-ba4f-54c5222633b9.png#clientId=u0f60a675-c98a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=464&id=ue17ce1ed&margin=%5Bobject%20Object%5D&name=image.png&originHeight=928&originWidth=1840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=124176&status=error&style=shadow&taskId=ue13ed0ff-e947-41dd-845a-a0178228517&title=&width=920)\n修改配置文件\n```properties\n\n#user  nobody;\nworker_processes  1;\n\nevents {\n    worker_connections  1024;\n}\n\n\nhttp {\n    include       mime.types;\n    default_type  application/json;\n    sendfile        on;\n    #tcp_nopush     on;\n\n    #keepalive_timeout  0;\n    keepalive_timeout  65;\n\n    #gzip  on;\n\n    server {\n        listen       8080;\n        server_name  localhost;\n\n        #charset koi8-r;\n\n        #access_log  logs/host.access.log  main;\n\n        location / {\n	# 加载/opt/homebrew/var/www下的打包文件\n            root   html/hmdp;\n            index  index.html index.htm;\n        }\n        error_page   500 502 503 504  /50x.html;\n        location = /50x.html {\n            root   html;\n        }\n	location /api {  \n            default_type  application/json;\n            #internal;  \n            keepalive_timeout   30s;  \n            keepalive_requests  1000;  \n            #支持keep-alive  \n            proxy_http_version 1.1;  \n            rewrite /api(/.*) $1 break;  \n            proxy_pass_request_headers on;\n            #more_clear_input_headers Accept-Encoding;  \n            proxy_next_upstream error timeout;  \n            proxy_pass http://127.0.0.1:8081;\n            #proxy_pass http://backend;\n        }\n    }\n\n	upstream backend {\n        	server 127.0.0.1:8081 max_fails=5 fail_timeout=10s weight=1;\n        	#server 127.0.0.1:8082 max_fails=5 fail_timeout=10s weight=1;\n    	}  \n    # include servers/*;\n}\n\n```\n启动nginx`brew services start nginx`\n访问 http://localhost:8080\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661567351914-55b833bb-d380-4ab7-9768-b6f73d94c30c.png#clientId=u0f60a675-c98a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=732&id=uf153d7b3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1464&originWidth=1480&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1592262&status=error&style=shadow&taskId=ue96fa137-c89c-4e88-b545-9942463770c&title=&width=740)\n### 基于Session实现登陆\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661603990152-715e772a-3152-4bff-a90e-7921c9fcfa40.png#clientId=u0f60a675-c98a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=543&id=ufffd65ce&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1086&originWidth=2262&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1030996&status=error&style=shadow&taskId=u3ce7c1d6-2946-4434-869e-2bd7180eae2&title=&width=1131)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661604343436-a6ff249f-056c-4b27-8c16-bd61f946d7e9.png#clientId=u0f60a675-c98a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=534&id=uf0f3e55e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1068&originWidth=2234&originalType=binary&ratio=1&rotation=0&showTitle=false&size=838999&status=error&style=shadow&taskId=uaa36cba1-f2c0-4644-b460-b7cb40b6701&title=&width=1117)\n#### 发送验证码\n```java\n/**\n     * 发送手机验证码\n     */\n    @PostMapping(\"code\")\n    public Result sendCode(@RequestParam(\"phone\") String phone, HttpSession session) {\n        //发送短信验证码并保存验证码\n\n        return userService.sendCode(phone,session);\n    }\n```\n```java\npublic interface IUserService extends IService<User> {\n\n    Result sendCode(String phone, HttpSession session);\n}\n```\n```java\n@Service\n@Slf4j\npublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {\n\n    @Override\n    public Result sendCode(String phone, HttpSession session) {\n        //校验手机号\n        if (RegexUtils.isPhoneInvalid(phone)) {\n            //如果不符合,返回错误信息\n            return Result.fail(\"手机号格式有误\");\n        }\n        //生成验证码 引入的工具类hutool\n        String code = RandomUtil.randomNumbers(6);\n        //保存验证码到session\n        session.setAttribute(\"code\",code);\n        //发送验证码,模拟一个,由于正常业务需要调用第三方服务\n        log.debug(\"发送验证码成功,验证码{}\",code);\n        //返回ok\n        return Result.ok();\n    }\n}\n```\n#### 短信验证码登陆\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661652400316-88655d25-9416-4e8c-91ea-77bc7f82e6d5.png#clientId=u76af2732-3bb2-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=556&id=udcdc770a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1112&originWidth=1992&originalType=binary&ratio=1&rotation=0&showTitle=false&size=384906&status=error&style=shadow&taskId=uad47c5cc-9d60-491e-94aa-6829df4320e&title=&width=996)\n```java\n/**\n     * 登录功能\n     * @param loginForm 登录参数，包含手机号、验证码；或者手机号、密码\n     */\n    @PostMapping(\"/login\")\n    public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){\n        // 实现登录功能\n        return userService.login(loginForm,session);\n    }\n```\n```java\n    @Override\n    public Result login(LoginFormDTO loginForm, HttpSession session) {\n        //校验手机号\n        if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {\n            //如果不符合,返回错误信息\n            return Result.fail(\"手机号格式有误\");\n        }\n        //提交手机号验证码,接收到封装到DTO中\n        //校验验证码\n        Object cacheCode = session.getAttribute(\"code\");\n        log.info(\"获取到session中的信息{}\",cacheCode);\n\n        if (!Objects.equals(cacheCode,loginForm.getCode())){\n            return Result.fail(\"验证码错误,请重新输入\");\n        }\n        //根据手机号查询用户\n        User user = this.query().eq(\"phone\", loginForm.getPhone()).one();\n        if (Objects.isNull(user)){\n            //用户不存在创建用户,保存到数据库\n            user = createUserWithPhone(loginForm.getPhone());\n        }\n        //保存用户到session中\n        session.setAttribute(\"user\",user);\n\n        return Result.ok();\n    }\n\n    private User createUserWithPhone(String phone) {\n        //创建用户\n        User user = new User();\n        user.setPhone(phone);\n        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX +RandomUtil.randomString(10));\n        //保存用户\n        this.save(user);\n        return user;\n    }\n```\n#### 登陆验证\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661658814594-75d1c4a3-fff1-48c0-bd60-a68212de247b.png#clientId=u76af2732-3bb2-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=545&id=u0776e08b&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1090&originWidth=2272&originalType=binary&ratio=1&rotation=0&showTitle=false&size=357150&status=error&style=shadow&taskId=u80e21d02-f98c-457a-ac99-217ccf70a32&title=&width=1136)\n```java\npackage com.hmdp.utils;\n\nimport com.hmdp.dto.UserDTO;\n\npublic class UserHolder {\n    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();\n\n    public static void saveUser(UserDTO user){\n        tl.set(user);\n    }\n\n    public static UserDTO getUser(){\n        return tl.get();\n    }\n\n    public static void removeUser(){\n        tl.remove();\n    }\n}\n\n```\n```java\n@Override\n    public Result login(LoginFormDTO loginForm, HttpSession session) {\n        //校验手机号\n        if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {\n            //如果不符合,返回错误信息\n            return Result.fail(\"手机号格式有误\");\n        }\n        //提交手机号验证码,接收到封装到DTO中\n        //校验验证码\n        Object cacheCode = session.getAttribute(\"code\");\n        log.info(\"获取到session中的信息{}\",cacheCode);\n\n        if (!Objects.equals(cacheCode,loginForm.getCode())){\n            return Result.fail(\"验证码错误,请重新输入\");\n        }\n        //根据手机号查询用户\n        User user = this.query().eq(\"phone\", loginForm.getPhone()).one();\n        if (Objects.isNull(user)){\n            //用户不存在创建用户,保存到数据库\n            user = createUserWithPhone(loginForm.getPhone());\n        }\n\n        //保存用户到session中,注意需要取掉敏感信息\n        /*spring提供的方法\n        UserDTO userDTO = new UserDTO();\n        BeanUtils.copyProperties(user,userDTO,UserDTO.class);\n        */\n        session.setAttribute(\"user\", BeanUtil.copyProperties(user,UserDTO.class));\n\n        return Result.ok();\n    }\n    private User createUserWithPhone(String phone) {\n        //创建用户\n        User user = new User();\n        user.setPhone(phone);\n        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX +RandomUtil.randomString(10));\n        //保存用户\n        this.save(user);\n        return user;\n    }\n```\n```java\npackage com.hmdp.utils;\n\nimport com.hmdp.dto.UserDTO;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\n\n@Component\npublic class LoginInterceptor implements HandlerInterceptor {\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n        //获取session\n        HttpSession session = request.getSession();\n        //获取session中的用户\n        Object user = session.getAttribute(\"user\");\n        //判断用户是否存在\n        if (user==null){\n            //不存在,拦截,返回401状态码\n            response.setStatus(401);\n            return false;\n        }\n        //存在,保存用户信息到ThreadLocal\n        UserHolder.saveUser((UserDTO) user);\n        //放行\n        return true;\n    }\n\n    @Override\n    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {\n        UserHolder.removeUser();\n        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);\n    }\n}\n\n```\n```java\npackage com.hmdp.config;\n\nimport com.hmdp.utils.LoginInterceptor;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n@Configuration\npublic class MvcConfig implements WebMvcConfigurer {\n    @Autowired\n    private LoginInterceptor loginInterceptor;\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n        registry.addInterceptor(loginInterceptor)\n                .excludePathPatterns(\n                        \"/user/code\",           //放行验证码请求\n                        \"/user/login\",          //放行登陆请求\n                        \"/blog/hot\",            //放行有关博客热点的请求\n                        \"/shop/**\",             //放行有关店铺的所有请求\n                        \"/shop-type/**\",         //放行有关店铺的所有请求\n                        \"/upload/**\",           //方便测试,放行上传\n                        \"/voucher/**\"           //方便测试,放行优惠券\n                );\n    }\n}\n\n```\n```java\n    @GetMapping(\"/me\")\n    public Result me(){\n        //获取当前登录的用户并返回\n        UserDTO user = UserHolder.getUser();\n        return Result.ok(user);\n    }\n```\n#### 集群的Session共享问题\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661663036301-4adf255e-7910-4981-9505-a9141617d973.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=503&id=u5465be3a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1006&originWidth=2306&originalType=binary&ratio=1&rotation=0&showTitle=false&size=576402&status=error&style=shadow&taskId=u20481480-a5b7-48a7-b785-6ab62b74119&title=&width=1153)\n### 基于Redis实现共享Session登陆\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661663588117-7c6cec68-2360-48c9-93e8-9dc2be29c99b.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=509&id=u4f3776e6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1018&originWidth=2086&originalType=binary&ratio=1&rotation=0&showTitle=false&size=502140&status=error&style=shadow&taskId=u84d68989-4fed-49c7-bdc7-f766f605c23&title=&width=1043)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661663426758-0823d7b6-cc53-4238-b394-d0822bfa4caa.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=489&id=u381fe233&margin=%5Bobject%20Object%5D&name=image.png&originHeight=978&originWidth=1728&originalType=binary&ratio=1&rotation=0&showTitle=false&size=336711&status=error&style=shadow&taskId=u4b1223f0-3899-4bef-95df-03634e0e94c&title=&width=864)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661663689148-e9be0185-5d1e-4bba-ba34-657b6d73ed49.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=512&id=u0f1a5449&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1024&originWidth=2132&originalType=binary&ratio=1&rotation=0&showTitle=false&size=541036&status=error&style=shadow&taskId=ua335f5e6-9d86-4ffa-a55b-7eea830a484&title=&width=1066)\n前端:\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661663858634-53f22c5b-5123-4a79-88a3-76b411666677.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=198&id=ubf77252e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=396&originWidth=906&originalType=binary&ratio=1&rotation=0&showTitle=false&size=169528&status=error&style=shadow&taskId=u9e2e775d-5878-4ee0-bc41-a9382a41c66&title=&width=453)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661663929727-d053066a-1c97-4e06-a614-74b5c8415926.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=232&id=ud6dbc413&margin=%5Bobject%20Object%5D&name=image.png&originHeight=464&originWidth=998&originalType=binary&ratio=1&rotation=0&showTitle=false&size=209259&status=error&style=shadow&taskId=u3f81fd72-550d-4bf5-91e5-944462958ec&title=&width=499)\n```java\npackage com.hmdp.utils;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.hmdp.dto.UserDTO;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\nimport javax.annotation.Resource;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport javax.servlet.http.HttpSession;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n@Component\n@Slf4j\npublic class LoginInterceptor implements HandlerInterceptor {\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n    //如果这个类不属于SpringBoot管理,那么需要通过构造器注入的方式,从注册拦截器地方获取到StringRedisTemplate这个对象\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n        //TODO 获取请求头中的token\n//        HttpSession session = request.getSession();\n        String token = request.getHeader(\"authorization\");\n        log.debug(\"前端请求头中的token信息: {}\",token);\n        //判断token是否为空白 null 空字符串：\"\" 空格、全角空格、制表符、换行符，等不可见字符\n        if (StrUtil.isBlank(token)){\n            //不存在,拦截,返回401状态码\n            response.setStatus(401);\n            return false;\n        }\n        //TODO 基于token获取redis中的用户\n//        Object user = session.getAttribute(\"user\");\n        Map<Object, Object> userMap = stringRedisTemplate.opsForHash()\n                .entries(RedisConstants.LOGIN_USER_KEY + token);\n        //判断用户是否存在\n        if (userMap.isEmpty()){\n            //不存在,拦截,返回401状态码\n            response.setStatus(401);\n            return false;\n        }\n        //TODO 将查询到的Hash数据转为UserDTO对象\n        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);\n\n        //存在,保存用户信息到ThreadLocal\n        UserHolder.saveUser(userDTO);\n        //TODO 刷新token的有效期\n        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);\n\n        //放行\n        return true;\n    }\n\n    @Override\n    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {\n        UserHolder.removeUser();\n    }\n}\n\n```\n```java\npackage com.hmdp.service.impl;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.bean.copier.CopyOptions;\nimport cn.hutool.core.lang.UUID;\nimport cn.hutool.core.util.RandomUtil;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.hmdp.dto.LoginFormDTO;\nimport com.hmdp.dto.Result;\nimport com.hmdp.dto.UserDTO;\nimport com.hmdp.entity.User;\nimport com.hmdp.mapper.UserMapper;\nimport com.hmdp.service.IUserService;\nimport com.hmdp.utils.RedisConstants;\nimport com.hmdp.utils.RegexUtils;\nimport com.hmdp.utils.SystemConstants;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.BeanUtils;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.convert.DurationUnit;\nimport org.springframework.data.redis.core.HashOperations;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.data.redis.core.ValueOperations;\nimport org.springframework.stereotype.Service;\n\nimport javax.annotation.Resource;\nimport javax.servlet.http.HttpSession;\nimport java.time.Duration;\nimport java.time.temporal.ChronoUnit;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\nimport static com.hmdp.utils.RedisConstants.*;\n\n/**\n * <p>\n * 服务实现类\n * </p>\n */\n@Service\n@Slf4j\npublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n\n    @Override\n    public Result sendCode(String phone, HttpSession session) {\n        //校验手机号\n        if (RegexUtils.isPhoneInvalid(phone)) {\n            //如果不符合,返回错误信息\n            return Result.fail(\"手机号格式有误\");\n        }\n        //生成验证码 引入的工具类hutool\n        String code = RandomUtil.randomNumbers(6);\n        //保存验证码到session\n//        session.setAttribute(\"code\",code);\n\n        //TODO 保存到Redis中 以手机号作为key保证唯一性 验证码作为值 并设置过期时间\n        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();\n        ops.set(RedisConstants.LOGIN_CODE_KEY +phone,\n                code,\n                RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);\n        //发送验证码,模拟一个,由于正常业务需要调用第三方服务\n        log.debug(\"发送验证码成功,验证码{}\",code);\n        //返回ok\n        return Result.ok();\n    }\n\n    @Override\n    public Result login(LoginFormDTO loginForm, HttpSession session) {\n        String phone = loginForm.getPhone();\n        //校验手机号\n        if (RegexUtils.isPhoneInvalid(phone)) {\n            //如果不符合,返回错误信息\n            return Result.fail(\"手机号格式有误\");\n        }\n        //提交手机号验证码,接收到封装到DTO中\n        //校验验证码\n//        Object cacheCode = session.getAttribute(\"code\");\n//        log.info(\"获取到session中的信息{}\",cacheCode);\n        //TODO 从Redis中获取验证码并校验\n        String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);\n        log.info(\"从Redis中获取的code: {}\",cacheCode);\n\n        if (!Objects.equals(cacheCode,loginForm.getCode())){\n            return Result.fail(\"验证码错误,请重新输入\");\n        }\n        //根据手机号查询用户\n        User user = this.query().eq(\"phone\", phone).one();\n        if (Objects.isNull(user)){\n            //用户不存在创建用户,保存到数据库\n            user = createUserWithPhone(phone);\n        }\n        //TODO 7.保存用户信息到redis中\n        //TODO 7.1随机生成token,作为登陆令牌\n        String token = UUID.randomUUID().toString(true);\n        //TODO 7.2将User对象转为Hash存储\n        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);\n\n        //这里需要做处理,id是Long类型的,而StringRedisTemplate接收的key都是String类型的,会报类型转换错误\n        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),\n                CopyOptions.create()\n                        .setIgnoreNullValue(true)//忽略null值\n                        .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));//设置字段值\n        log.debug(\"map:{}\",userMap.toString());\n        //TODO 7.3存储\n        //保存用户到session中,注意需要取掉敏感信息\n//        session.setAttribute(\"user\", BeanUtil.copyProperties(user,UserDTO.class));\n        HashOperations<String, Object, Object> ops = stringRedisTemplate.opsForHash();\n        String tokenKey = LOGIN_USER_KEY + token;\n        ops.putAll(tokenKey, userMap);\n        //防止用户过多导致Redis中数据量过大\n        stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);\n\n        //TODO 8.返回token\n        return Result.ok(token);\n    }\n\n    private User createUserWithPhone(String phone) {\n        //创建用户\n        User user = new User();\n        user.setPhone(phone);\n        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX +RandomUtil.randomString(10));\n        //保存用户\n        this.save(user);\n        return user;\n    }\n}\n\n```\n\n#### 登陆拦截器的优化\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661670024449-cec7da9e-8307-4a2f-bd32-9f18b666fbde.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=556&id=u11c5a32d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1112&originWidth=2174&originalType=binary&ratio=1&rotation=0&showTitle=false&size=417380&status=error&style=shadow&taskId=u28c926fc-262c-4a3a-a7f7-06fd77a88c0&title=&width=1087)\n```java\npackage com.hmdp.utils;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.hmdp.dto.UserDTO;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\nimport javax.annotation.Resource;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\n@Component\n@Slf4j\npublic class RefreshTokenInterceptor implements HandlerInterceptor {\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n    //如果这个类不属于SpringBoot管理,那么需要通过构造器注入的方式,从注册拦截器地方获取到StringRedisTemplate这个对象\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n        //TODO 获取请求头中的token\n//        HttpSession session = request.getSession();\n        String token = request.getHeader(\"authorization\");\n        log.debug(\"前端请求头中的token信息: {}\",token);\n        //判断token是否为空白 null 空字符串：\"\" 空格、全角空格、制表符、换行符，等不可见字符\n        if (StrUtil.isBlank(token)){\n            return true;\n        }\n        //TODO 基于token获取redis中的用户\n//        Object user = session.getAttribute(\"user\");\n        Map<Object, Object> userMap = stringRedisTemplate.opsForHash()\n                .entries(RedisConstants.LOGIN_USER_KEY + token);\n        //判断用户是否存在\n        if (userMap.isEmpty()){\n            return true;\n        }\n        //TODO 将查询到的Hash数据转为UserDTO对象\n        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);\n\n        //存在,保存用户信息到ThreadLocal\n        UserHolder.saveUser(userDTO);\n        //TODO 刷新token的有效期\n        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);\n\n        //放行\n        return true;\n    }\n\n    @Override\n    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {\n        UserHolder.removeUser();\n    }\n}\n\n```\n```java\npackage com.hmdp.utils;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\n@Component\n@Slf4j\npublic class LoginInterceptor implements HandlerInterceptor {\n\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n        //判断是否需要拦截(ThreadLocal中是否有用户)\n        if (UserHolder.getUser()==null){\n            response.setStatus(401);\n            return false;\n        }\n        //有用户 放行\n        return true;\n    }\n\n    @Override\n    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {\n        UserHolder.removeUser();\n    }\n}\n\n```\n```java\npackage com.hmdp.config;\n\nimport com.hmdp.utils.LoginInterceptor;\nimport com.hmdp.utils.RefreshTokenInterceptor;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n@Configuration\npublic class MvcConfig implements WebMvcConfigurer {\n    @Autowired\n    private LoginInterceptor loginInterceptor;\n    @Autowired\n    private RefreshTokenInterceptor refreshTokenInterceptor;\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n        //登陆拦截器\n        registry.addInterceptor(loginInterceptor)\n                .excludePathPatterns(\n                        \"/user/code\",           //放行验证码请求\n                        \"/user/login\",          //放行登陆请求\n                        \"/blog/hot\",            //放行有关博客热点的请求\n                        \"/shop/**\",             //放行有关店铺的所有请求\n                        \"/shop-type/**\",         //放行有关店铺的所有请求\n                        \"/upload/**\",           //方便测试,放行上传\n                        \"/voucher/**\"           //方便测试,放行优惠券\n                ).order(1);//order 执行先后 值小先执行\n        //token刷新拦截器\n        registry.addInterceptor(refreshTokenInterceptor).addPathPatterns(\"/**\").order(0);//拦截所有请求\n    }\n}\n\n```\n### 登陆业务流程分析\n\n- 首先用户请求页面，前端向后端发送请求(在请求拦截器中，每一个请求都需要)，并使请求头中携带sessionStorage中的token信息(直接获取就OK了，没有则是null，后端处理)\n- 然后经过后端的一级拦截器 (拦截所有请求，根据token获取redis中的用户信息，并且保存到当前线程中，可以让用户访问不需要登陆的页面也可以**进行token的过期时间的刷新**，如果没有登陆即：没有token或者token信息在redis中没有数据直接放行到二级拦截器统一处理拦截，处理需要登陆的请求)\n- 二级拦截器只处理需要用户登陆后才能进行的操作，如果当前线程没有用户信息则响应401状态码并且拦截(前端接收并跳转到登陆页面，让用户先登陆再操作)，如果有用户信息则放行\n- 用户登陆：发送验证码：首先校验手机号，生成验证码，以统一前缀+手机号作为Redis的key，验证码作为value，并且设置过期时间，然后发送验证码，返回成功结果\n- 用户接收到验证码，填写验证码登陆，发送登陆请求，后端处理登陆请求：校验手机号，根据统一前缀+手机号从Redis中获取验证码并验证；根据手机号查询数据库中是否存在该用户(存在则进行后续操作，不存在则向数据库中添加数据注册)；保存用户信息到Redis【生成随机token(UUID)，以Hash类型把**脱敏**后的用户信息存到redis中，并初次设置过期时间，返回token】\n- 前端接收到token后，把token存储到sessionStorage中(随着浏览器的关闭而销毁)，之后的每次请求都在请求头中携带这个token，经过一级拦截器判断token，根据token从redis中获取数据，把数据写到ThreadLocal当前线程对象中，然后更新token的过期时间\n\n![](https://cdn.nlark.com/yuque/0/2022/jpeg/26314652/1661742586539-4a6ab82d-e29e-40a8-8c92-8b1e5f84db7a.jpeg)\n\n## 商户查询缓存\n### 什么是缓存\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661743191863-816c940c-eb72-4c49-9ebb-97ba3c7198e8.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=531&id=u92810c6f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1062&originWidth=2166&originalType=binary&ratio=1&rotation=0&showTitle=false&size=251282&status=error&style=shadow&taskId=uf434feae-148a-4d3d-bf67-64b37edde8e&title=&width=1083)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661743351485-c81986a9-47cc-43c2-8975-b69764b8ca36.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=420&id=u20c8ab81&margin=%5Bobject%20Object%5D&name=image.png&originHeight=840&originWidth=2212&originalType=binary&ratio=1&rotation=0&showTitle=false&size=412375&status=error&style=shadow&taskId=uac046d99-77d4-48ec-b2f4-07ed9cc0d89&title=&width=1106)\n\n### 添加Redis缓存\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661744014737-2a5a9d62-880d-4dfa-b7c9-6ab119c62501.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=504&id=ue249d174&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1008&originWidth=2192&originalType=binary&ratio=1&rotation=0&showTitle=false&size=358546&status=error&style=shadow&taskId=u49f24743-4cc5-4395-a289-d402201d893&title=&width=1096)\n```java\n@RestController\n@RequestMapping(\"/shop\")\npublic class ShopController {\n\n    @Resource\n    public IShopService shopService;\n\n    /**\n     * 根据id查询商铺信息\n     * @param id 商铺id\n     * @return 商铺详情数据\n     */\n    @GetMapping(\"/{id}\")\n    public Result queryShopById(@PathVariable(\"id\") Long id) {\n//        return Result.ok(shopService.getById(id));\n        return shopService.queryById(id);\n    }\n}\n```\n```java\npublic interface IShopService extends IService<Shop> {\n\n    Result queryById(Long id);\n}\n```\n```java\n@Service\npublic class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    @Override\n    public Result queryById(Long id) {\n        String key = RedisConstants.CACHE_SHOP_KEY + id;\n        //1.从redis中查询商铺缓存\n        String shopJSON = stringRedisTemplate.opsForValue().get(key);\n        //2.判断是否存在\n        if (StrUtil.isNotBlank(shopJSON)) {\n            //3.存在,返回\n            Shop shop = JSONUtil.toBean(shopJSON, Shop.class);\n            return Result.ok(shop);\n        }\n        //4.不存在 ,根据id查询数据库\n        Shop shop = this.getById(id);\n        //5.数据库中也不存在,返回错误\n        if (shop==null){\n            return Result.fail(\"店铺不存在\");\n        }\n        //6.数据库中存在,写入到redis中\n        String jsonStr = JSONUtil.toJsonStr(shop);\n        stringRedisTemplate.opsForValue().set(key,jsonStr);\n        return Result.ok(shop);\n    }\n}\n\n```\n### 缓存更新策略\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661756747730-bac5a4a1-5486-46da-823b-0c9a9db85f88.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=547&id=u975fee7c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1094&originWidth=2320&originalType=binary&ratio=1&rotation=0&showTitle=false&size=488493&status=error&style=shadow&taskId=ua6fbcb30-69fb-4c24-83a0-e260bcbc4a9&title=&width=1160)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661757243770-70b232f2-7eba-4efe-b490-d0af2958b6fc.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=504&id=ub7d9f950&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1008&originWidth=2224&originalType=binary&ratio=1&rotation=0&showTitle=false&size=579511&status=error&style=shadow&taskId=u57f06575-9f25-40d2-a466-c08f021c7dc&title=&width=1112)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661757585279-f7bf31c4-98b3-4d68-84a5-04059f3ad2eb.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=555&id=u80566a7d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1110&originWidth=2138&originalType=binary&ratio=1&rotation=0&showTitle=false&size=388049&status=error&style=shadow&taskId=u6f4b01ac-06fe-4123-90ce-5cad5bca9c7&title=&width=1069)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661757685640-b676ef86-36e1-4d51-9409-c88ad62edd5a.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=488&id=u52d298dc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=976&originWidth=2202&originalType=binary&ratio=1&rotation=0&showTitle=false&size=659993&status=error&style=shadow&taskId=u43126843-c2d3-4d91-9382-74b7aad3ff3&title=&width=1101)\n```java\n    /**\n     * 实现数据库和缓存数据的读写一致\n     * @param shop\n     * @return\n     */\n    @Override\n    @Transactional//开启事务,保证出现异常可以回滚数据\n    public Result update(Shop shop) {\n        if (shop.getId()==null) {\n            return Result.fail(\"店铺id不能为空\");\n        }\n        //1.更新数据库\n        this.updateById(shop);\n        //2.删除缓存\n        stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + shop.getId());\n        return Result.ok();\n    }\n```\n\n### 缓存穿透\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661758935589-512f1e26-8b92-43b7-8ec7-c91c7ac0d4c7.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=485&id=u3f90d103&margin=%5Bobject%20Object%5D&name=image.png&originHeight=970&originWidth=2314&originalType=binary&ratio=1&rotation=0&showTitle=false&size=517867&status=error&style=shadow&taskId=u2ecdfa2c-d209-4992-9c24-493cae507bc&title=&width=1157)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661759056439-62d0edab-0257-41cb-a884-6bb027af3d23.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=514&id=u3457ea34&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1028&originWidth=2348&originalType=binary&ratio=1&rotation=0&showTitle=false&size=519663&status=error&style=shadow&taskId=u3452fe11-6e08-4e2f-a08a-e3132f160f0&title=&width=1174)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661760246636-641aab0f-8d31-4e4c-97a4-7b7c55f4f39c.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=518&id=u2e7320a8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1036&originWidth=2070&originalType=binary&ratio=1&rotation=0&showTitle=false&size=400283&status=error&style=shadow&taskId=u9b467979-ec26-49de-aad8-c095be4481f&title=&width=1035)\n```java\n@Service\npublic class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    @Override\n    public Result queryById(Long id) {\n        String key = RedisConstants.CACHE_SHOP_KEY + id;\n        //1.从redis中查询商铺缓存\n        String shopJSON = stringRedisTemplate.opsForValue().get(key);\n        //2.判断是否存在商铺信息\n        if (StrUtil.isNotBlank(shopJSON)) {\n            //3.存在,返回\n            Shop shop = JSONUtil.toBean(shopJSON, Shop.class);\n            return Result.ok(shop);\n        }\n        //判断是否为空字符串  ->   防止缓存穿透\n//        if (shopJSON!=null){ //经过上面的if判断过滤后,剩下的只有 null 或者 \"\" 或者 不可见字符\n        if (\"\".equals(shopJSON)){\n            return Result.fail(\"查询的商铺不存在\");\n        }\n        //4.不存在 ,根据id查询数据库\n        Shop shop = this.getById(id);\n        //5.数据库中也不存在,返回错误\n        if (shop==null){\n            //将空字符串写入到缓存中,并设置2分钟的过期时间\n            stringRedisTemplate.opsForValue().set(key,\"\",SHOP_NULL_TTL,TimeUnit.MINUTES);\n\n            return Result.fail(\"店铺不存在\");\n        }\n        //6.数据库中存在,写入到redis中,并设置超时时间\n        String jsonStr = JSONUtil.toJsonStr(shop);\n        stringRedisTemplate.opsForValue().set(key,jsonStr,SHOP_ID_TTL, TimeUnit.MINUTES);\n        return Result.ok(shop);\n    }\n```\n\n\n### 缓存雪崩\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661760847699-5abf2944-df7e-4242-bb47-d8aeb2df6013.png#clientId=u1c18d00a-3ff7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=476&id=u8dbb4247&margin=%5Bobject%20Object%5D&name=image.png&originHeight=952&originWidth=2226&originalType=binary&ratio=1&rotation=0&showTitle=false&size=446092&status=error&style=shadow&taskId=u58dea793-c9a2-4e10-a02e-e731cba5278&title=&width=1113)\n### 缓存击穿\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661823692158-69261a3e-5990-4b59-b248-768ae469aadb.png#clientId=u206f30d9-d0cc-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=513&id=u8781b591&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1026&originWidth=2346&originalType=binary&ratio=1&rotation=0&showTitle=false&size=393171&status=error&style=shadow&taskId=u853d438e-1b66-41d6-88dd-aaa15c3285f&title=&width=1173)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661824026953-1ba3bf75-2241-4f09-bc53-d6c444cdb639.png#clientId=u206f30d9-d0cc-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=608&id=u4a568c0a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1216&originWidth=2434&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1298669&status=error&style=shadow&taskId=uffdac726-c90b-42f6-8138-ba49832778f&title=&width=1217)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661824492552-cecfca06-b194-4e81-bcc0-fc579bc70f0e.png#clientId=u206f30d9-d0cc-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=460&id=ubf83b1d6&margin=%5Bobject%20Object%5D&name=image.png&originHeight=920&originWidth=2214&originalType=binary&ratio=1&rotation=0&showTitle=false&size=295846&status=error&style=shadow&taskId=u0a1b606d-ab5d-43a7-b530-1de379ef906&title=&width=1107)\n#### 基于互斥锁解决缓存击穿问题\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661831520324-b886dc09-b55e-4827-84da-88ac92a35d0b.png#clientId=u206f30d9-d0cc-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=483&id=u69d1e82f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=966&originWidth=1536&originalType=binary&ratio=1&rotation=0&showTitle=false&size=361491&status=error&style=shadow&taskId=u25082037-71ab-4ac9-ac12-89b2c383614&title=&width=768)\n```java\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n    /**\n     * 添加锁 如果不存在\n     * @param key 锁名\n     * @return boolean\n     */\n    @Override\n    public Result queryById(Long id) {\n        //缓存穿透\n//        Shop shop = queryWithPassThrough(id);\n        //缓存击穿 互斥锁\n        Shop shop = queryWithMutex(id);\n        if (shop==null){\n            return Result.fail(\"店铺不存在\");\n        }\n        return Result.ok(shop);\n    }\n    private boolean tryLock(String key){\n        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, \"1\", 10, TimeUnit.SECONDS);\n        return BooleanUtil.isTrue(flag);\n    }\n\n    /**\n     * 释放锁\n     * @param key\n     */\n    private void unLock(String key){\n        stringRedisTemplate.delete(key);\n    }\n    /**\n     * 互斥锁 解决缓存穿击\n     * @param id\n     * @return\n     */\n    public Shop queryWithMutex(Long id){\n        String key = RedisConstants.CACHE_SHOP_KEY + id;\n        //1.从redis中查询商铺缓存\n        String shopJSON = stringRedisTemplate.opsForValue().get(key);\n        //2.判断是否存在商铺信息\n        if (StrUtil.isNotBlank(shopJSON)) {\n            //3.存在,返回\n            Shop shop = JSONUtil.toBean(shopJSON, Shop.class);\n            return shop;\n        }\n        //判断是否为空字符串  ->   防止缓存穿透\n//        if (shopJSON!=null){ //经过上面的if判断过滤后,剩下的只有 null 或者 \"\" 或者 不可见字符\n        if (\"\".equals(shopJSON)){\n            return null;\n        }\n\n        //TODO 开始实现缓存重建\n        String lockKey= \"lock:shop:\"+id;\n        Shop shop;\n        try {\n            //1.获取互斥锁\n            boolean isLock = tryLock(lockKey);\n            //2.判断是否获取成功\n            if (!isLock){\n                //获取锁失败,休眠,并重试\n                Thread.sleep(50);\n                return queryWithMutex(id);\n            }\n\n            //region Description DoubleCheck\n            //TODO 再次检查redis缓存是否存在 DoubleCheck\n            // 防止第一个线程释放锁之后第二个线程立马获取到了锁(第二个线程前面的redis缓存已经检查过了发现没有缓存,但是此时第一个线程已经重建了缓存)\n            // 防止重复更新缓存\n            //1.从redis中查询商铺缓存\n            String doubleCheckShopJSON = stringRedisTemplate.opsForValue().get(key);\n            //2.判断是否存在商铺信息\n            if (StrUtil.isNotBlank(doubleCheckShopJSON)) {\n                //3.存在,返回\n                return JSONUtil.toBean(doubleCheckShopJSON, Shop.class);\n            }\n            //判断是否为空字符串  ->   防止缓存穿透\n//        if (shopJSON!=null){ //经过上面的if判断过滤后,剩下的只有 null 或者 \"\" 或者 不可见字符\n            if (\"\".equals(doubleCheckShopJSON)){\n                return null;\n            }\n            //endregion\n\n            //TODO 获取锁成功 重建缓存数据\n            //4.如果成功,根据id查询数据库\n            //根据id查询数据库\n            shop = this.getById(id);\n            //5.数据库中也不存在,返回错误\n            if (shop==null){\n                //将空字符串写入到缓存中,并设置2分钟的过期时间\n                stringRedisTemplate.opsForValue().set(key,\"\",SHOP_NULL_TTL,TimeUnit.MINUTES);\n\n                return null;\n            }\n            //6.数据库中存在,写入到redis中,并设置超时时间\n            String jsonStr = JSONUtil.toJsonStr(shop);\n            stringRedisTemplate.opsForValue().set(key,jsonStr,SHOP_ID_TTL, TimeUnit.MINUTES);\n        } catch (InterruptedException e) {\n            throw new RuntimeException(e);\n        } finally {\n            //TODO 释放互斥锁\n            unLock(lockKey);\n\n        }\n        // 返回\n        return shop;\n\n    }\n```\n\n#### 基于逻辑过期解决缓存击穿问题\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661831902001-015cf317-d0ad-48bd-8432-de70b491b49b.png#clientId=u206f30d9-d0cc-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=467&id=ue9855d51&margin=%5Bobject%20Object%5D&name=image.png&originHeight=934&originWidth=1840&originalType=binary&ratio=1&rotation=0&showTitle=false&size=671590&status=error&style=shadow&taskId=u149ae6b3-ea23-4e90-8dbf-e420e0f7506&title=&width=920)\n```java\n@Service\npublic class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    @Override\n    public Result queryById(Long id) {\n        //缓存空字符串解决 缓存穿透\n//        Shop shop = queryWithPassThrough(id);\n        //互斥锁解决 缓存击穿\n//        Shop shop = queryWithMutex(id);\n        //逻辑过期解决 缓存击穿\n        Shop shop = queryWithLogicExpire(id);\n        if (shop==null){\n            return Result.fail(\"店铺不存在\");\n        }\n        return Result.ok(shop);\n    }\n\n    //创建线程池\n    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);\n    public Shop queryWithLogicExpire(Long id){\n        String key = RedisConstants.CACHE_SHOP_KEY + id;\n        //1.从redis中查询商铺缓存\n        String shopJSON = stringRedisTemplate.opsForValue().get(key);\n        //2.判断是否存在商铺信息\n        if (StrUtil.isBlank(shopJSON)) {\n            //3.不存在返回null\n            return null;\n        }\n        //存在 反序列化\n        RedisData redisData = JSONUtil.toBean(shopJSON, RedisData.class);\n        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);\n        LocalDateTime expireTime = redisData.getExpireTime();\n        //判断是否过期\n        if (expireTime.isAfter(LocalDateTime.now())){\n            //未过期,直接返回\n            return shop;\n        }\n        //已过期,执行重建\n        //缓存重建\n        //获取互斥锁\n        String lockKey = LOCK_SHOP_KEY+id;\n        boolean isLock = tryLock(lockKey);\n        //判断是否获取锁成功\n        if (isLock){\n            //开启独立线程执行重建\n            CACHE_REBUILD_EXECUTOR.submit(()->{\n                try {\n                    //创建缓存 设置逻辑过期时间为30分钟\n                    saveShop2Redis(id,30L*60);\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                } finally {\n                    //释放锁\n                    unLock(lockKey);\n                }\n            });\n        }\n\n        //返回过期的信息\n        return shop;\n    }\n\n    /**\n     * 执行逻辑过期数据的重建\n     * @param id 商品id\n     * @param expireSeconds 多少秒后过期\n     */\n    public void saveShop2Redis(Long id, Long expireSeconds){\n        //根据id查询数据库\n        Shop shop = this.getById(id);\n        //封装逻辑过期时间\n        RedisData redisData = new RedisData();\n        redisData.setData(shop);\n        //逻辑过期\n        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));\n        //写入到redis中\n        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(redisData));\n    }\n    /**\n     * 添加锁 如果不存在\n     * @param key 锁名\n     * @return boolean\n     */\n    private boolean tryLock(String key){\n        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, \"1\", 10, TimeUnit.SECONDS);\n        return BooleanUtil.isTrue(flag);\n    }\n\n    /**\n     * 释放锁\n     * @param key\n     */\n    private void unLock(String key){\n        stringRedisTemplate.delete(key);\n    }\n```\n### 缓存工具封装\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1661836343871-9b3c963f-d6d0-4759-85ee-b7b1a4339723.png#clientId=u206f30d9-d0cc-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=259&id=u83f7b656&margin=%5Bobject%20Object%5D&name=image.png&originHeight=518&originWidth=1978&originalType=binary&ratio=1&rotation=0&showTitle=false&size=364409&status=error&style=shadow&taskId=uf0fa4b32-265a-468e-8346-b27c61144ec&title=&width=989)\n```java\npackage com.hmdp.utils;\n\nimport cn.hutool.core.util.BooleanUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONObject;\nimport cn.hutool.json.JSONUtil;\nimport com.hmdp.entity.Shop;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.TimeUnit;\nimport java.util.function.Function;\n\nimport static com.hmdp.utils.RedisConstants.*;\n\n@Component\n@Slf4j\npublic class CacheClient {\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n\n    /**\n     * 将任意java对象序列化为json并存储在string类型的key中,并且可以设置过期时间\n     * @param key redis的key名称\n     * @param value 存储任意java对象\n     * @param time 过期时间\n     * @param unit 时间单位\n     */\n    public void set(String key, Object value, Long time, TimeUnit unit){\n        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);\n    }\n\n    /**\n     * 写值并设置 逻辑过期时间\n     * @param key redis的key名称\n     * @param value 存储任意java对象\n     * @param time 逻辑过期时间\n     * @param unit 时间单位\n     */\n    public void setWithLogicExpire(String key, Object value, Long time, TimeUnit unit){\n        RedisData redisData = new RedisData();\n        redisData.setData(value);\n        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));\n        //写入Redis\n        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));\n    }\n\n    /**\n     * 缓存空字符串 解决缓存穿透 缓存空字符串固定时间2分钟\n     * @param keyPrefix key前缀\n     * @param id 唯一id\n     * @param type 返回值类型\n     * @param dbFallback 查询数据库函数\n     * @param time 逻辑过期时间\n     * @param unit 时间单位\n     * @param <R> 返回值类型\n     * @param <ID> id类型\n     * @return 查询的信息\n     */\n    public <R, ID> R queryWithPassThrough(\n            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){\n        String key = keyPrefix + id;\n        //1.从redis中查询商铺缓存\n        String json = stringRedisTemplate.opsForValue().get(key);\n        //2.判断是否存在商铺信息\n        if (StrUtil.isNotBlank(json)) {\n            //3.存在,返回\n            return JSONUtil.toBean(json, type);\n        }\n        //判断是否为空字符串  ->   防止缓存穿透\n//        if (shopJSON!=null){ //经过上面的if判断过滤后,剩下的只有 null 或者 \"\" 或者 不可见字符\n        if (\"\".equals(json)){\n            return null;\n        }\n        //4.不存在 ,根据id查询数据库\n        R r = dbFallback.apply(id);\n        //5.数据库中也不存在,返回错误\n        if (r==null){\n            //将空字符串写入到缓存中,并设置2分钟的过期时间\n            stringRedisTemplate.opsForValue().set(key,\"\",SHOP_NULL_TTL,TimeUnit.MINUTES);\n            return null;\n        }\n        //6.数据库中存在,写入到redis中,并设置超时时间\n        this.set(key,r,time,unit);\n        return r;\n    }\n\n    //创建线程池\n    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);\n\n    /**\n     * 逻辑过期解决 缓存击穿\n     * @param keyPrefix key前缀\n     * @param id 唯一id\n     * @param type 返回值类型\n     * @param dbFallback 查询数据库函数\n     * @param time 逻辑过期时间\n     * @param unit 时间单位\n     * @param <R> 返回值类型\n     * @param <ID> id类型\n     * @return 查询的信息\n     */\n    public <R, ID> R queryWithLogicExpire(\n            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){\n        String key = keyPrefix + id;\n        //1.从redis中查询商铺缓存\n        String json = stringRedisTemplate.opsForValue().get(key);\n        //2.判断是否存在商铺信息\n        if (StrUtil.isBlank(json)) {\n            //3.不存在返回null\n            return null;\n        }\n        //存在 反序列化\n        RedisData redisData = JSONUtil.toBean(json, RedisData.class);\n        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);\n        LocalDateTime expireTime = redisData.getExpireTime();\n        //判断是否过期\n        if (expireTime.isAfter(LocalDateTime.now())){\n            //未过期,直接返回\n            return r;\n        }\n        //已过期,执行重建\n        //缓存重建\n        //获取互斥锁\n        String lockKey = LOCK_SHOP_KEY+id;\n        boolean isLock = tryLock(lockKey);\n        //判断是否获取锁成功\n        if (isLock){\n            //开启独立线程执行重建\n            CACHE_REBUILD_EXECUTOR.submit(()->{\n                try {\n                    //重建缓存 设置逻辑过期时间为30分钟\n                    R r1 = dbFallback.apply(id);\n                    this.setWithLogicExpire(key,r1,time,unit);\n\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                } finally {\n                    //释放锁\n                    unLock(lockKey);\n                }\n            });\n        }\n\n        //返回过期的信息\n        return r;\n    }\n    /**\n     * 添加锁 如果不存在\n     * @param key 锁名\n     * @return boolean\n     */\n    private boolean tryLock(String key){\n        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, \"1\", 10, TimeUnit.SECONDS);\n        return BooleanUtil.isTrue(flag);\n    }\n\n    /**\n     * 释放锁\n     * @param key 锁名\n     */\n    private void unLock(String key){\n        stringRedisTemplate.delete(key);\n    }\n}\n\n```\n## 优惠券秒杀\n### 全局唯一ID\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662111343607-3281368d-78d5-4346-a831-2e932cb4ad3f.png#clientId=u3c6a1dfe-c3cb-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=448&id=ua7dee91f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=896&originWidth=2306&originalType=binary&ratio=1&rotation=0&showTitle=false&size=298823&status=error&style=shadow&taskId=ucb1b5486-b18c-4cd4-bfb1-167a317a3af&title=&width=1153)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662112274359-3230275b-ffcc-42a7-882c-2e6f295507f6.png#clientId=u3c6a1dfe-c3cb-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=528&id=u9cfa9b32&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1056&originWidth=2238&originalType=binary&ratio=1&rotation=0&showTitle=false&size=642290&status=error&style=shadow&taskId=uab66a9d3-feff-4204-9c0f-78cb4c0d66c&title=&width=1119)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662112481699-579f5717-ce72-41f6-b9e0-eb9310b89093.png#clientId=u3c6a1dfe-c3cb-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=468&id=u5f8d3d77&margin=%5Bobject%20Object%5D&name=image.png&originHeight=936&originWidth=2088&originalType=binary&ratio=1&rotation=0&showTitle=false&size=325498&status=error&style=shadow&taskId=u7535ca8a-314d-450a-8ab7-9536f332cfb&title=&width=1044)\n```java\npackage com.hmdp.utils;\n\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.data.redis.core.ValueOperations;\nimport org.springframework.stereotype.Component;\n\nimport javax.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.time.ZoneOffset;\nimport java.time.format.DateTimeFormatter;\n\n/**\n * Redis id 生成器\n */\n@Component\npublic class RedisIdWorker {\n/*    LocalDateTime time = LocalDateTime.of(2022, 1, 1, 0, 0, 0);\n      long second = time.toEpochSecond(ZoneOffset.UTC);\n*/\n    private static final long BEGIN_TIMESTAMP = 1640995200L;\n\n    /**\n     * 序列号的位数\n     */\n    private static final int COUNT_BITS = 32;\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n    public long nextId(String keyPrefix){\n        //1.生成时间戳\n        LocalDateTime now = LocalDateTime.now();//当前的秒数\n        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);\n        long timestamp = nowSecond - BEGIN_TIMESTAMP;\n        //2.生成序列号\n        //2.1获取当前日期,精确到天\n        String date = now.format(DateTimeFormatter.ofPattern(\"yyyy:MM:dd\"));\n        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();\n        long increment = ops.increment(\"icr:\" + keyPrefix + \":\" + date);\n\n        //3.拼接返回\n        //将时间戳左移32位 在使用或运算\n        return timestamp << COUNT_BITS | increment;\n    }\n\n\n}\n\n```\n```java\npackage com.hmdp;\n\nimport com.hmdp.utils.RedisIdWorker;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.context.SpringBootTest;\n\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\n@SpringBootTest\nclass HmDianPingApplicationTests {\n\n    @Autowired\n    private RedisIdWorker redisIdWorker;\n\n    private ExecutorService executorService = Executors.newFixedThreadPool(500);\n    @Test\n    void testRedisIdWorker() throws InterruptedException {\n        CountDownLatch countDownLatch = new CountDownLatch(300);\n        long before = System.currentTimeMillis();\n        //300个线程\n        for (int i = 0; i < 300; i++) {\n            executorService.submit(()->{\n                //每个线程执行100次\n                for (int j = 0; j < 100; j++) {\n                    long id = redisIdWorker.nextId(\"order\");\n                    System.out.println(\"id = \" + id);\n                }\n                countDownLatch.countDown();;\n            });\n        }\n        countDownLatch.await();\n        long after = System.currentTimeMillis();\n        long time = after - before;\n        System.out.println(\"time = \" + time);\n\n    }\n\n\n}\n\n```\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662119028733-bdc564ec-c2bc-40cf-a25d-83216f8c08e5.png#clientId=u3c6a1dfe-c3cb-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=385&id=u5f35edfc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=770&originWidth=2256&originalType=binary&ratio=1&rotation=0&showTitle=false&size=269072&status=error&style=shadow&taskId=u94b1a7ed-86d0-47da-8360-7bc032a1448&title=&width=1128)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662119043582-7840cae7-3fa9-4023-ae7b-ccf15ac90afd.png#clientId=u3c6a1dfe-c3cb-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=875&id=u6c570494&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1750&originWidth=2880&originalType=binary&ratio=1&rotation=0&showTitle=false&size=651710&status=error&style=shadow&taskId=ubdc67dbb-af10-4ed3-8ee1-e661724735e&title=&width=1440)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662119071424-4da571f8-f3cc-48e4-af9d-3bb6dcf0f6d5.png#clientId=u3c6a1dfe-c3cb-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=828&id=u6d8fa9d2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1656&originWidth=2360&originalType=binary&ratio=1&rotation=0&showTitle=false&size=175540&status=error&style=shadow&taskId=u2fc92af4-121c-4747-9651-2355f4a9156&title=&width=1180)\n### 添加优惠券\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662248855487-65f06174-b0a6-4ee2-a8fa-f3b3e5f5db13.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=447&id=u36eead8a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=894&originWidth=2116&originalType=binary&ratio=1&rotation=0&showTitle=false&size=693836&status=error&style=shadow&taskId=u7663bc9a-3b41-4c38-a6f6-304d4b457cf&title=&width=1058)\n使用postman给商户添加优惠券-秒杀券\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662266392844-1fcd245f-e8fd-4e60-8444-24a81094db34.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=514&id=uf8f78b79&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1028&originWidth=2018&originalType=binary&ratio=1&rotation=0&showTitle=false&size=182869&status=error&style=shadow&taskId=ud1207984-0c7e-438f-9e8c-ec622364219&title=&width=1009)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662266614200-6ae02b9f-0b09-4c62-89c1-16674dd130f0.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=293&id=u2e404fee&margin=%5Bobject%20Object%5D&name=image.png&originHeight=586&originWidth=2510&originalType=binary&ratio=1&rotation=0&showTitle=false&size=232093&status=error&style=shadow&taskId=uda9e555c-cdd8-4d9c-88b9-ff827d673ab&title=&width=1255)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662266456326-706a12fc-3a69-4761-ade5-433ac7ec52d8.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=665&id=u054af01d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1330&originWidth=728&originalType=binary&ratio=1&rotation=0&showTitle=false&size=464147&status=error&style=shadow&taskId=u2181b184-6e37-499f-819c-2b202d6fc1f&title=&width=364)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662266700826-a5a380b0-05c3-4665-b29d-8656e8b05ef9.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=495&id=uae979c99&margin=%5Bobject%20Object%5D&name=image.png&originHeight=990&originWidth=2548&originalType=binary&ratio=1&rotation=0&showTitle=false&size=958937&status=error&style=shadow&taskId=ub7b91090-94ec-49a3-bfa2-7769ed36ba1&title=&width=1274)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662266780355-3254beb5-92c2-4067-afaf-b950e1dd39d0.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=511&id=uf35c21c7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1022&originWidth=2298&originalType=binary&ratio=1&rotation=0&showTitle=false&size=571363&status=error&style=shadow&taskId=ufcd62be9-ef77-4bd2-9dc3-bcc5b35e7e4&title=&width=1149)\n### 实现秒杀下单\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662267323700-1b617756-28c4-43ad-9406-5b2d9db31cbc.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=576&id=u81e91478&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1152&originWidth=1798&originalType=binary&ratio=1&rotation=0&showTitle=false&size=583443&status=error&style=shadow&taskId=u3352f671-6d20-4f70-9d36-deba1f2bf88&title=&width=899)\n```java\n@Service\npublic class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {\n\n    @Autowired\n    private ISeckillVoucherService seckillVoucherService;\n    @Resource\n    private RedisIdWorker redisIdWorker;\n\n\n    @Override\n    @Transactional\n    public Result seckillVoucher(Long voucherId) {\n        //查询优惠券\n        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);\n        //判断秒杀是否开始\n        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {\n            //尚未开始\n            return Result.fail(\"秒杀尚未开始\");\n        }\n        //判断是否结束\n        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {\n            return Result.fail(\"秒杀已结束\");\n        }\n        //判断库存是否充足\n        if (voucher.getStock() < 1) {\n            return Result.fail(\"库存不足\");\n        }\n        //扣减库存\n        boolean success = seckillVoucherService.update()\n                .setSql(\"stock = stock - 1\")\n                .eq(\"voucher_id\", voucherId).update();\n        if (!success){\n            return Result.fail(\"库存不足\");\n        }\n        //创建订单\n        VoucherOrder voucherOrder = new VoucherOrder();\n        //订单id\n        long orderId = redisIdWorker.nextId(\"order\");\n        voucherOrder.setId(orderId);\n        //用户id\n        Long userId = UserHolder.getUser().getId();\n        voucherOrder.setUserId(userId);\n        //代金券id\n        voucherOrder.setVoucherId(voucherId);\n\n        //保存优惠券订单\n        save(voucherOrder);\n\n        //返回订单id\n        return Result.ok(orderId);\n    }\n}\n\n```\n不过由于多线程并发的问题，会出现优惠券多卖的现象，导致商家亏损。(在判断库存的时候，几乎同时进行了判断库存，发现都充足，于是优惠券会多卖)。解决方案加锁\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662269416972-78e4c830-dad3-4c2f-865e-d99d7c6194c7.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=540&id=u4a129e1c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1080&originWidth=2210&originalType=binary&ratio=1&rotation=0&showTitle=false&size=541091&status=error&style=shadow&taskId=u456b6143-e14d-4d65-89b3-86fbb1013b2&title=&width=1105)\n### 乐观锁\n#### 版本号法\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662269814936-4c59690f-47b8-4c4a-a789-2b8d3bfc2db3.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=550&id=ua916fecf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1100&originWidth=2272&originalType=binary&ratio=1&rotation=0&showTitle=false&size=367868&status=error&style=shadow&taskId=uce736f8d-96e4-40ed-af73-dfee5b9d1b0&title=&width=1136)\n#### CAS法\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662269929443-a9d8b2ee-4ab2-456e-b945-bc8c22db5fec.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=490&id=uaed7e1eb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=980&originWidth=2260&originalType=binary&ratio=1&rotation=0&showTitle=false&size=298516&status=error&style=shadow&taskId=ufdf81f46-9db9-4b2a-9d44-532612e804c&title=&width=1130)\n```java\n//扣减库存 使用乐观锁的CAS法,防止超卖\n        boolean success = seckillVoucherService.update()\n                .setSql(\"stock = stock - 1\")//set stock = stock - 1\n                .eq(\"voucher_id\", voucherId).eq(\"stock\",voucher.getStock())//where voucher_id = #{voucherId} and stock = #{stock}\n                .update();\n```\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662270643294-abea646c-33e9-4e5d-995f-e1eabeb0bdea.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=79&id=ud690c256&margin=%5Bobject%20Object%5D&name=image.png&originHeight=158&originWidth=2036&originalType=binary&ratio=1&rotation=0&showTitle=false&size=164912&status=error&style=shadow&taskId=u534dbfeb-1a94-4ffb-b32a-d72e7734ce8&title=&width=1018)\n发现失败率大大增加，因为如果同一时刻有一百的线程去抢购剩余量为100的优惠券，有一个先抢到了，库存会减一，那么其他的99个线程就会都失败，因为查询到的剩余是100，而现在是99了，又使用的是相等条件，所以其余的失败了\n我们可以这样修改 只要优惠券的库存大于0，就可以抢购\n```java\n//扣减库存 使用乐观锁的CAS法,防止超卖\n        boolean success = seckillVoucherService.update()\n                .setSql(\"stock = stock - 1\")//set stock = stock - 1\n                .eq(\"voucher_id\", voucherId).gt(\"stock\",0)//where voucher_id = #{voucherId} and stock > 0 //防止失败率过高\n                .update();\n```\n完整代码：\n```java\n@Service\npublic class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {\n\n    @Autowired\n    private ISeckillVoucherService seckillVoucherService;\n    @Resource\n    private RedisIdWorker redisIdWorker;\n\n\n    @Override\n    @Transactional\n    public Result seckillVoucher(Long voucherId) {\n        //查询优惠券\n        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);\n        //判断秒杀是否开始\n        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {\n            //尚未开始\n            return Result.fail(\"秒杀尚未开始\");\n        }\n        //判断是否结束\n        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {\n            return Result.fail(\"秒杀已结束\");\n        }\n        //判断库存是否充足\n        if (voucher.getStock() < 1) {\n            return Result.fail(\"库存不足\");\n        }\n        //扣减库存 使用乐观锁的CAS法,防止超卖\n        boolean success = seckillVoucherService.update()\n                .setSql(\"stock = stock - 1\")//set stock = stock - 1\n//                .eq(\"voucher_id\", voucherId).eq(\"stock\",voucher.getStock())//where voucher_id = #{voucherId} and stock = #{stock}\n                .eq(\"voucher_id\", voucherId).gt(\"stock\",0)//where voucher_id = #{voucherId} and stock > 0 //防止失败率过高\n                .update();\n        if (!success){\n            return Result.fail(\"库存不足\");\n        }\n        //创建订单\n        VoucherOrder voucherOrder = new VoucherOrder();\n        //订单id\n        long orderId = redisIdWorker.nextId(\"order\");\n        voucherOrder.setId(orderId);\n        //用户id\n        Long userId = UserHolder.getUser().getId();\n        voucherOrder.setUserId(userId);\n        //代金券id\n        voucherOrder.setVoucherId(voucherId);\n\n        //保存优惠券订单\n        save(voucherOrder);\n\n        //返回订单id\n        return Result.ok(orderId);\n    }\n}\n\n```\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662270534403-bcbdda03-b3cf-484d-89f5-46c5702f5cbf.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=207&id=u4b28daa2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=414&originWidth=2604&originalType=binary&ratio=1&rotation=0&showTitle=false&size=142250&status=error&style=shadow&taskId=uac5d7c5f-1abb-432f-8cca-3c5ac7111e1&title=&width=1302)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662270596721-286df415-9c7d-44ad-a389-50ef4f196938.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=79&id=u6ddee484&margin=%5Bobject%20Object%5D&name=image.png&originHeight=158&originWidth=2030&originalType=binary&ratio=1&rotation=0&showTitle=false&size=171885&status=error&style=shadow&taskId=u1d014d32-3029-490f-9a4f-beb1cd0a9e0&title=&width=1015)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662271065019-b75e45c6-2a92-4514-8fc6-0bd45e5997b1.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=383&id=u0d374035&margin=%5Bobject%20Object%5D&name=image.png&originHeight=766&originWidth=2078&originalType=binary&ratio=1&rotation=0&showTitle=false&size=348193&status=error&style=shadow&taskId=u950fc562-fa32-4975-a468-5a7590b89e3&title=&width=1039)\n### 一人一单\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662271278107-8e7c9005-2fc0-4eea-bdcf-832763b3f606.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=533&id=uc13c86f2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1066&originWidth=2216&originalType=binary&ratio=1&rotation=0&showTitle=false&size=344093&status=error&style=shadow&taskId=u3604604e-4c0d-40ee-bfde-157ab1135a6&title=&width=1108)\n```java\npackage com.hmdp.service.impl;\n\nimport com.hmdp.dto.Result;\nimport com.hmdp.entity.SeckillVoucher;\nimport com.hmdp.entity.VoucherOrder;\nimport com.hmdp.mapper.VoucherOrderMapper;\nimport com.hmdp.service.ISeckillVoucherService;\nimport com.hmdp.service.IVoucherOrderService;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.hmdp.utils.RedisIdWorker;\nimport com.hmdp.utils.UserHolder;\nimport org.springframework.aop.framework.AopContext;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport javax.annotation.Resource;\nimport java.time.LocalDateTime;\n\n\n@Service\npublic class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {\n\n    @Autowired\n    private ISeckillVoucherService seckillVoucherService;\n    @Resource\n    private RedisIdWorker redisIdWorker;\n\n\n    @Override\n    public Result seckillVoucher(Long voucherId) {\n        //查询优惠券\n        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);\n        //判断秒杀是否开始\n        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {\n            //尚未开始\n            return Result.fail(\"秒杀尚未开始\");\n        }\n        //判断是否结束\n        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {\n            return Result.fail(\"秒杀已结束\");\n        }\n        //判断库存是否充足\n        if (voucher.getStock() < 1) {\n            return Result.fail(\"库存不足\");\n        }\n        \n        //使用用户的id作为锁,由于toString方法底层还是调用了new String() 每次还是产生了一个新的对象 所以调用intern()方法返回在字符串常量池中已经存在的字符串,就是同一个字符串对象了\n        Long userId = UserHolder.getUser().getId();\n        synchronized (userId.toString().intern()) {\n            //获取代理对象(事务)\n            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();\n            return proxy.createVoucherOrder(voucherId);\n        }\n    }\n\n    @Transactional\n    public Result createVoucherOrder(Long voucherId) {\n        Long userId = UserHolder.getUser().getId();\n        //由于事务是整个方法的,当方法执行完毕的时候才会提交事务,而锁在这里只锁住了部分,在并发业务中还是有可能出现并发问题的,所以在这个地方加锁还是不合理的\n        //使用用户的id作为锁,由于toString方法底层还是调用了new String() 每次还是产生了一个新的对象 所以调用intern()方法返回在字符串常量池中已经存在的字符串,就是同一个字符串对象了\n//        synchronized (userId.toString().intern()) {\n            //一人一单\n            //查询订单\n            int count = query().eq(\"user_id\", userId).eq(\"voucher_id\", voucherId).count();\n            //判断是否存在\n            if (count>0){\n                //用户已经购买过\n                return Result.fail(\"不能重复抢购\");\n            }\n\n            //扣减库存 使用乐观锁的CAS法,防止超卖\n            boolean success = seckillVoucherService.update()\n                    .setSql(\"stock = stock - 1\")//set stock = stock - 1\n    //                .eq(\"voucher_id\", voucherId).eq(\"stock\",voucher.getStock())//where voucher_id = #{voucherId} and stock = #{stock}\n                    .eq(\"voucher_id\", voucherId).gt(\"stock\",0)//where voucher_id = #{voucherId} and stock > 0 //防止失败率过高\n                    .update();\n            if (!success){\n                return Result.fail(\"库存不足\");\n            }\n\n            //创建订单\n            VoucherOrder voucherOrder = new VoucherOrder();\n            //订单id\n            long orderId = redisIdWorker.nextId(\"order\");\n            voucherOrder.setId(orderId);\n            //用户id\n            voucherOrder.setUserId(userId);\n            //代金券id\n            voucherOrder.setVoucherId(voucherId);\n\n            //保存优惠券订单\n            save(voucherOrder);\n\n            //返回订单id\n            return Result.ok(orderId);\n//        }\n    }\n}\n\n```\n200次并发请求，期望只有一个成功\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662273112403-95216a29-5fd1-4e5b-aaa9-99df2724dbda.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=89&id=u276bc0ba&margin=%5Bobject%20Object%5D&name=image.png&originHeight=178&originWidth=2028&originalType=binary&ratio=1&rotation=0&showTitle=false&size=285043&status=error&style=shadow&taskId=u2fdba0b0-a0ab-4704-80e9-4d3d057e6c0&title=&width=1014)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662273133607-88471ab4-b70f-4dae-95d3-5094ecfc16a6.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=50&id=u39e8c87d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=100&originWidth=886&originalType=binary&ratio=1&rotation=0&showTitle=false&size=72642&status=error&style=shadow&taskId=u4605452e-2fce-4ecd-b312-0ff700228bf&title=&width=443)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662273145619-b0a2e189-f1fc-4089-afb1-4d40d19d2846.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=49&id=u7806530d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=98&originWidth=1120&originalType=binary&ratio=1&rotation=0&showTitle=false&size=58113&status=error&style=shadow&taskId=u8cd4761f-98c3-41f4-8ff5-8ac7616f851&title=&width=560)\n完成一人一单\n### 集群下的线程并发安全问题\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662273288977-6dfda77b-54d2-47ea-9c03-d35547da442d.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=499&id=u74951354&margin=%5Bobject%20Object%5D&name=image.png&originHeight=998&originWidth=2060&originalType=binary&ratio=1&rotation=0&showTitle=false&size=491246&status=error&style=shadow&taskId=udea161be-c391-4f59-a894-61aa48cec25&title=&width=1030)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662274100639-3993044c-845e-4a81-9c03-280cea86be55.png#clientId=uec46adeb-aa88-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=551&id=u66d459c8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1102&originWidth=2258&originalType=binary&ratio=1&rotation=0&showTitle=false&size=558088&status=error&style=shadow&taskId=ue7d7a214-8bb3-47f7-b887-4f5681ab954&title=&width=1129)\n在分布式系统下，每一个系统都对应着自己的JVM和TomCat，所以我们上面加锁就在分布式系统下失效了，解决方案`分布式锁`\n### 分布式锁\n#### 原理和不同方式实现对比\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662297472386-05df33e7-598b-4919-a1a9-d1eaed865aa9.png#clientId=u5b9707e8-f50a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=558&id=u7a8cd572&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1116&originWidth=2292&originalType=binary&ratio=1&rotation=0&showTitle=false&size=527433&status=error&style=shadow&taskId=ucba10b91-219d-41a3-aabe-f5beff31824&title=&width=1146)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662297637248-e5863500-8802-4fce-8fef-a6b33020363a.png#clientId=u5b9707e8-f50a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=464&id=u4dfa8b81&margin=%5Bobject%20Object%5D&name=image.png&originHeight=928&originWidth=2198&originalType=binary&ratio=1&rotation=0&showTitle=false&size=214788&status=error&style=shadow&taskId=u50a57654-08e8-4a1d-8eca-93e2d516895&title=&width=1099)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662298042071-6ff53572-b5fc-44ec-91aa-91405fb3cad2.png#clientId=u5b9707e8-f50a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=517&id=uc9ccb4ca&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1034&originWidth=2240&originalType=binary&ratio=1&rotation=0&showTitle=false&size=389913&status=error&style=shadow&taskId=u632b9ad6-49cc-408b-b321-dd0f60467ca&title=&width=1120)\n#### Redis的分布式锁实现思路\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662298508323-5014cfae-b7bb-4622-a0f8-607a614c7c4a.png#clientId=u5b9707e8-f50a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=454&id=u2d40bb39&margin=%5Bobject%20Object%5D&name=image.png&originHeight=908&originWidth=1990&originalType=binary&ratio=1&rotation=0&showTitle=false&size=355361&status=error&style=shadow&taskId=u3eedc38b-1d1c-4a49-9d10-0efb92c4903&title=&width=995)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662298571133-22f4f1e2-b7c8-4193-833b-abab1ec80633.png#clientId=u5b9707e8-f50a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=490&id=uaddf9919&margin=%5Bobject%20Object%5D&name=image.png&originHeight=980&originWidth=934&originalType=binary&ratio=1&rotation=0&showTitle=false&size=144904&status=error&style=shadow&taskId=ue964b44b-a8a1-4405-aca0-17ae4ffbe64&title=&width=467)\n#### 实现Redis分布式锁版本1\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662366343219-3f6fc6f6-8eab-4235-8d2f-b22fe01f71d0.png#clientId=u9e3d665c-ca9a-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=472&id=u389d8aaf&margin=%5Bobject%20Object%5D&name=image.png&originHeight=944&originWidth=2072&originalType=binary&ratio=1&rotation=0&showTitle=false&size=380446&status=error&style=shadow&taskId=ub43fb20b-c6ba-440a-88b5-6256a2e35ba&title=&width=1036)\n```java\npackage com.hmdp.utils;\n\npublic interface ILock {\n    /**\n     * 尝试获取锁\n     * @param timeoutSec 锁持有的时间,过期后自动释放\n     * @return 是否获取锁成功\n     */\n    boolean tryLock(long timeoutSec);\n\n    /**\n     * 释放锁\n     */\n    void unlock();\n}\n\n```\n```java\npackage com.hmdp.utils;\n\nimport cn.hutool.core.util.BooleanUtil;\nimport org.springframework.data.redis.core.StringRedisTemplate;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class SimpleRedisLock implements ILock{\n    private String name;\n    private StringRedisTemplate stringRedisTemplate;\n\n    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {\n        this.name = name;\n        this.stringRedisTemplate = stringRedisTemplate;\n    }\n\n    //统一前缀\n    private static final String KEY_PREFIX = \"lock:\";\n    @Override\n    public boolean tryLock(long timeoutSec) {\n        //获取线程表示\n        String key = KEY_PREFIX + name;\n        long threadId = Thread.currentThread().getId();\n        //获取锁\n        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key, threadId+\"\", timeoutSec, TimeUnit.SECONDS);\n        return BooleanUtil.isTrue(success);\n        //Boolean.TRUE.equals(bool);\n    }\n\n    @Override\n    public void unlock() {\n        //释放锁\n        stringRedisTemplate.delete(KEY_PREFIX+name);\n\n    }\n}\n\n```\n```java\npackage com.hmdp.service.impl;\n\nimport com.hmdp.dto.Result;\nimport com.hmdp.entity.SeckillVoucher;\nimport com.hmdp.entity.VoucherOrder;\nimport com.hmdp.mapper.VoucherOrderMapper;\nimport com.hmdp.service.ISeckillVoucherService;\nimport com.hmdp.service.IVoucherOrderService;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.hmdp.utils.RedisIdWorker;\nimport com.hmdp.utils.SimpleRedisLock;\nimport com.hmdp.utils.UserHolder;\nimport org.springframework.aop.framework.AopContext;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport javax.annotation.Resource;\nimport java.time.LocalDateTime;\n\n@Service\npublic class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {\n\n    @Autowired\n    private ISeckillVoucherService seckillVoucherService;\n    @Resource\n    private RedisIdWorker redisIdWorker;\n    @Autowired\n    private StringRedisTemplate stringRedisTemplate;\n\n\n    @Override\n    public Result seckillVoucher(Long voucherId) {\n        //查询优惠券\n        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);\n        //判断秒杀是否开始\n        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {\n            //尚未开始\n            return Result.fail(\"秒杀尚未开始\");\n        }\n        //判断是否结束\n        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {\n            return Result.fail(\"秒杀已结束\");\n        }\n        //判断库存是否充足\n        if (voucher.getStock() < 1) {\n            return Result.fail(\"库存不足\");\n        }\n\n        //使用用户的id作为锁,由于toString方法底层还是调用了new String() 每次还是产生了一个新的对象 所以调用intern()方法返回在字符串常量池中已经存在的字符串,就是同一个字符串对象了\n        Long userId = UserHolder.getUser().getId();\n//        synchronized (userId.toString().intern()) {\n        //基于Redis的分布式锁\n        //创建锁对象\n        SimpleRedisLock lock = new SimpleRedisLock(\"order:\" + userId, stringRedisTemplate);\n        //尝试获取锁 并设置超时时间\n        boolean isLock = lock.tryLock(5);\n        //判断是否获取锁成功\n        if (!isLock){\n            //获取锁失败\n            return Result.fail(\"不允许重复下单\");\n        }\n        try {\n            //获取代理对象(事务)\n            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();\n            return proxy.createVoucherOrder(voucherId);\n        } finally {\n            //释放锁\n            lock.unlock();\n        }\n//        }\n    }\n\n    @Transactional\n    public Result createVoucherOrder(Long voucherId) {\n        Long userId = UserHolder.getUser().getId();\n        //由于事务是整个方法的,当方法执行完毕的时候才会提交事务,而锁在这里只锁住了部分,在并发业务中还是有可能出现并发问题的,所以在这个地方加锁还是不合理的\n        //使用用户的id作为锁,由于toString方法底层还是调用了new String() 每次还是产生了一个新的对象 所以调用intern()方法返回在字符串常量池中已经存在的字符串,就是同一个字符串对象了\n//        synchronized (userId.toString().intern()) {\n            //一人一单\n            //查询订单\n            int count = query().eq(\"user_id\", userId).eq(\"voucher_id\", voucherId).count();\n            //判断是否存在\n            if (count>0){\n                //用户已经购买过\n                return Result.fail(\"不能重复抢购\");\n            }\n\n            //扣减库存 使用乐观锁的CAS法,防止超卖\n            boolean success = seckillVoucherService.update()\n                    .setSql(\"stock = stock - 1\")//set stock = stock - 1\n    //                .eq(\"voucher_id\", voucherId).eq(\"stock\",voucher.getStock())//where voucher_id = #{voucherId} and stock = #{stock}\n                    .eq(\"voucher_id\", voucherId).gt(\"stock\",0)//where voucher_id = #{voucherId} and stock > 0 //防止失败率过高\n                    .update();\n            if (!success){\n                return Result.fail(\"库存不足\");\n            }\n\n            //创建订单\n            VoucherOrder voucherOrder = new VoucherOrder();\n            //订单id\n            long orderId = redisIdWorker.nextId(\"order\");\n            voucherOrder.setId(orderId);\n            //用户id\n            voucherOrder.setUserId(userId);\n            //代金券id\n            voucherOrder.setVoucherId(voucherId);\n\n            //保存优惠券订单\n            save(voucherOrder);\n\n            //返回订单id\n            return Result.ok(orderId);\n//        }\n    }\n}\n\n```\n#### Redis分布式锁误删问题\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662379070263-7a311f4e-e37c-415b-a8e8-1e326f3c48df.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=522&id=u857a5e8a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1044&originWidth=2252&originalType=binary&ratio=1&rotation=0&showTitle=false&size=344861&status=error&style=shadow&taskId=u49624f67-a2a3-4205-926a-7568581db49&title=&width=1126)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662379126066-626edab8-1702-4267-a968-894530e4936e.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=460&id=u785da6a4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=920&originWidth=898&originalType=binary&ratio=1&rotation=0&showTitle=false&size=169312&status=error&style=shadow&taskId=ud6c1f548-0831-462c-8d16-c883a92a31f&title=&width=449)\n> 由于我们获取锁的时候是使用`前缀+标识+userId`作为锁的key；`线程ID`作为锁的值，当同一个用户在执行业务的时候，这个时候A线程正在执行由于种种原因被阻塞了，导致锁被超时释放了，而这个时候线程B执行获取到了锁，然后在线程B执行业务中，线程A完成了任务并且把锁删除了(删除了B的锁)，导致B没有锁了，所以出现了分布式锁误删问题\n\n> 解决方案: 在删除锁的时候，获取到锁的值，把该值与当前线程值比较，如果一样则释放锁，否则不处理。\n> 推荐使用UUID作为锁的值，因为在分布式系统下，有可能出现两个线程id重复的现象\n\n#### 解决Redis锁的误删问题\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662379692881-dea93048-2fd3-4afa-8e4b-8fe4e9844df9.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=347&id=u8edc1f40&margin=%5Bobject%20Object%5D&name=image.png&originHeight=694&originWidth=1854&originalType=binary&ratio=1&rotation=0&showTitle=false&size=278258&status=error&style=shadow&taskId=u57415d11-e210-4b82-880e-dd884990158&title=&width=927)\n```java\npackage com.hmdp.utils;\n\nimport cn.hutool.core.lang.UUID;\nimport cn.hutool.core.util.BooleanUtil;\nimport org.springframework.data.redis.core.StringRedisTemplate;\n\nimport java.util.concurrent.TimeUnit;\n\npublic class SimpleRedisLock implements ILock{\n    private String name;\n    private StringRedisTemplate stringRedisTemplate;\n\n    public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {\n        this.name = name;\n        this.stringRedisTemplate = stringRedisTemplate;\n    }\n\n    //统一前缀\n    private static final String KEY_PREFIX = \"lock:\";\n    //value前缀使用UUDI\n    private static final String ID_PREFIX = UUID.randomUUID().toString(true)+\"-\";\n    @Override\n    public boolean tryLock(long timeoutSec) {\n        //获取线程标识\n        //key\n        String key = KEY_PREFIX + name;\n        //value\n        String threadId = ID_PREFIX+Thread.currentThread().getId();\n        //获取锁\n        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key, threadId, timeoutSec, TimeUnit.SECONDS);\n        return BooleanUtil.isTrue(success);\n        //Boolean.TRUE.equals(bool);\n    }\n\n    @Override\n    public void unlock() {\n        //获取线程标识\n        String threadId = ID_PREFIX+Thread.currentThread().getId();\n        //获取锁中的标识\n        String key = KEY_PREFIX + name;\n        String id = stringRedisTemplate.opsForValue().get(key);\n        //判断是否一致\n        if (threadId.equals(id)){\n            //一致释放锁\n            stringRedisTemplate.delete(KEY_PREFIX+name);\n        }\n    }\n}\n\n```\n#### 分布式锁的原子性问题\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662380827938-389dd090-4f34-42fa-adc6-5772753fa26b.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=551&id=ufc0c6d39&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1102&originWidth=2296&originalType=binary&ratio=1&rotation=0&showTitle=false&size=584899&status=error&style=shadow&taskId=u7cadaa44-0b94-40d1-8628-b6809db927e&title=&width=1148)\n> 在线程一判断成功在释放锁之前发生了阻塞jvm垃圾回收导致，锁超时释放了；此时线程二获取锁执行业务，这个时候线程一恢复正常，删除锁，这删除的还是线程二的锁\n\n#### Lua脚本解决多条命令原子性问题\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662382383174-cb86fc7f-8e9e-4827-8c81-2f1a47f7c0fe.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=560&id=u9c6e95e2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1120&originWidth=2092&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1002589&status=error&style=shadow&taskId=u4b63f2ce-8dfb-4a36-b1a4-f893b18a096&title=&width=1046)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662382576615-3d2fcde5-7f35-4ba5-9b1d-99c8bc347591.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=441&id=u1ad442db&margin=%5Bobject%20Object%5D&name=image.png&originHeight=882&originWidth=1950&originalType=binary&ratio=1&rotation=0&showTitle=false&size=468759&status=error&style=shadow&taskId=ucef2e4e1-92a6-4956-8c9c-6efa7f316c8&title=&width=975)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662383749283-ceb4b57f-0056-4482-8c4b-4ac8755110d3.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=193&id=udeae0af9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=386&originWidth=2210&originalType=binary&ratio=1&rotation=0&showTitle=false&size=341008&status=error&style=shadow&taskId=uec1bcb50-290b-45f5-a510-229b713e249&title=&width=1105)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662383780924-e208be7a-94c1-45c7-868c-1fd17188f895.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=443&id=u129c3705&margin=%5Bobject%20Object%5D&name=image.png&originHeight=886&originWidth=1460&originalType=binary&ratio=1&rotation=0&showTitle=false&size=146346&status=error&style=shadow&taskId=u86aa6bb8-db1b-4d5e-a4c1-a788156fed0&title=&width=730)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662384199089-28ae2299-84bf-4817-b226-c0af5fc7e91d.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=460&id=ud8034d16&margin=%5Bobject%20Object%5D&name=image.png&originHeight=920&originWidth=1728&originalType=binary&ratio=1&rotation=0&showTitle=false&size=449821&status=error&style=shadow&taskId=ubf1292ce-8061-4d9e-8c96-fb7c1db97c7&title=&width=864)\n```lua\n-- 锁的key\nlocal key = KEYS[1]\n\n-- 当前线程标识\nlocal threadId = ARGV[1]\n\n-- 获取锁中的线程标识 get key\nlocal id = redis.call(\'get\',key)\n\n-- 比较线程标识与锁中的标识是否一致\nif(id == threadId) then\n    -- 释放锁 del key\n    return redis.call(\'del\',key)\nend\nreturn 0\n\n-- 简化\n-- 锁的key\n-- local key = KEYS[1]\n\n-- 当前线程标识\n-- local threadId = ARGV[1]\n\n-- 获取锁中的线程标识 get key\n-- local id = redis.call(\'get\',KEYS[1])\n\n-- 比较线程标识与锁中的标识是否一致\nif(redis.call(\'get\',KEYS[1]) == ARGV[1]) then\n    -- 释放锁 del key\n    return redis.call(\'del\',KEYS[1])\nend\nreturn 0\n```\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662385460445-511cf718-1c2a-443c-beed-123614e42d56.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=875&id=u2e864e76&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1750&originWidth=2880&originalType=binary&ratio=1&rotation=0&showTitle=false&size=579650&status=error&style=shadow&taskId=u673fff44-05fc-4c06-a6a9-e82309962f1&title=&width=1440)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662385518522-ba639666-5472-4be2-a134-e77d29331e4b.png#clientId=u401f1361-699f-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=378&id=u4be26b6d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=756&originWidth=2066&originalType=binary&ratio=1&rotation=0&showTitle=false&size=373187&status=error&style=shadow&taskId=ub9c7302b-1241-4f57-a783-4e0b52c8443&title=&width=1033)\n#### Redisson功能介绍\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1662467451301-b2124444-7308-4fc2-bb5a-3b5901be3e9e.png#clientId=u319cd4f5-f8a7-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=546&id=u763fea0a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1092&originWidth=2238&originalType=binary&ratio=1&rotation=0&showTitle=false&size=468327&status=error&style=shadow&taskId=u0d2c5c1a-fb9c-423c-b3d7-21075af8830&title=&width=1119)\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1663486125353-9602f449-59e8-4713-a937-2cf0d8c6ce16.png#clientId=u51b8986e-d3f6-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=551&id=u61d08e77&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1102&originWidth=2302&originalType=binary&ratio=1&rotation=0&showTitle=false&size=644826&status=error&style=shadow&taskId=u76436897-4993-452a-8a6f-7815b7619b0&title=&width=1151)\n##### Redisson快速入门\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1663486297183-b86e5249-ea41-4ccb-b438-4716d4fe01ec.png#clientId=u51b8986e-d3f6-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=538&id=uf5349799&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1076&originWidth=2172&originalType=binary&ratio=1&rotation=0&showTitle=false&size=515063&status=error&style=shadow&taskId=ua65d8efa-6689-4915-98c1-843fb144d09&title=&width=1086)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1663486864964-7fc95221-3b04-4ebc-a8eb-9a8f3422ba07.png#clientId=u51b8986e-d3f6-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=466&id=ucf24c628&margin=%5Bobject%20Object%5D&name=image.png&originHeight=932&originWidth=1906&originalType=binary&ratio=1&rotation=0&showTitle=false&size=461475&status=error&style=shadow&taskId=u6c637081-689c-4d96-9481-1e4f7c39b03&title=&width=953)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1663487822330-781db28a-9223-4a39-a6fa-67f0b5e63efb.png#clientId=u51b8986e-d3f6-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=475&id=u14aa7dbb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=950&originWidth=1702&originalType=binary&ratio=1&rotation=0&showTitle=false&size=175633&status=error&style=shadow&taskId=ub4227abc-4caf-44fa-8abe-f29ec442de0&title=&width=851)\n##### Redisson的可重入锁的原理\n![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1663488464827-a90e435e-f9ed-4232-aedd-b78b09772948.png#clientId=u51b8986e-d3f6-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=561&id=ua5c50937&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1122&originWidth=2354&originalType=binary&ratio=1&rotation=0&showTitle=false&size=784570&status=error&style=shadow&taskId=ub17ed69f-bf22-4764-b2f9-786ffbb1228&title=&width=1177)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1663488991865-f9fcd341-1834-4f26-a392-88057f0bf11f.png#clientId=u51b8986e-d3f6-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=559&id=u5275f93f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1118&originWidth=2306&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1026151&status=error&style=shadow&taskId=u459769f6-c701-4a1b-b1fe-8deaf0e2fca&title=&width=1153)![image.png](https://cdn.nlark.com/yuque/0/2022/png/26314652/1663489091110-08d43e4e-6fd7-44ee-85ba-a124997208ee.png#clientId=u51b8986e-d3f6-4&crop=0&crop=0&crop=1&crop=1&errorMessage=unknown%20error&from=paste&height=570&id=uc2da7620&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1140&originWidth=2346&originalType=binary&ratio=1&rotation=0&showTitle=false&size=982784&status=error&style=shadow&taskId=u8a1939a5-eced-40ed-bb51-1ba3a2b9af2&title=&width=1173)\n\n', 287, 0, 0, '2022-09-09 15:12:59', '2022-12-09 11:28:28', 20, 0);
INSERT INTO `tb_blog` (`blog_id`, `user_id`, `blog_cover`, `blog_title`, `blog_description`, `blog_content`, `blog_views`, `blog_comment_count`, `blog_like_count`, `create_time`, `update_time`, `version`, `deleted`) VALUES (2, 8888888888888888888, '../../images/blog/post-1.png', '基于Vue+SpringBoot前后端分离的个人博客系统', '未添加相关描述的博客', '#基于Vue+SpringBoot前后端分离的个人博客系统\\n##博客系统介绍', 994, 0, 0, '2022-09-09 15:12:59', '2022-09-10 11:04:24', 5, 0);
INSERT INTO `tb_blog` (`blog_id`, `user_id`, `blog_cover`, `blog_title`, `blog_description`, `blog_content`, `blog_views`, `blog_comment_count`, `blog_like_count`, `create_time`, `update_time`, `version`, `deleted`) VALUES (3, 8888888888888888888, '../../images/blog/post-1.png', '基于Vue+SpringBoot前后端分离的个人博客系统', '未添加相关描述的博客', '#基于Vue+SpringBoot前后端分离的个人博客系统\\n##博客系统介绍', 31, 0, 0, '2022-09-09 15:12:59', '2022-09-10 11:04:24', 1, 0);
INSERT INTO `tb_blog` (`blog_id`, `user_id`, `blog_cover`, `blog_title`, `blog_description`, `blog_content`, `blog_views`, `blog_comment_count`, `blog_like_count`, `create_time`, `update_time`, `version`, `deleted`) VALUES (4, 8888888888888888888, '../../images/blog/post-1.png', '基于Vue+SpringBoot前后端分离的个人博客系统', '未添加相关描述的博客', '#基于Vue+SpringBoot前后端分离的个人博客系统\\n##博客系统介绍', 10220, 0, 0, '2022-09-09 15:39:58', '2022-09-10 11:04:24', 0, 0);
INSERT INTO `tb_blog` (`blog_id`, `user_id`, `blog_cover`, `blog_title`, `blog_description`, `blog_content`, `blog_views`, `blog_comment_count`, `blog_like_count`, `create_time`, `update_time`, `version`, `deleted`) VALUES (5, 8888888888888888888, '../../images/blog/post-1.png', '基于Vue+SpringBoot前后端分离的个人博客系统', '未添加相关描述的博客', '#基于Vue+SpringBoot前后端分离的个人博客系统\\n##博客系统介绍', 30220, 0, 0, '2022-09-09 15:12:59', '2022-09-10 11:04:24', 0, 0);
COMMIT;

-- ----------------------------
-- Table structure for tb_blog_label
-- ----------------------------
DROP TABLE IF EXISTS `tb_blog_label`;
CREATE TABLE `tb_blog_label` (
  `blog_id` bigint NOT NULL COMMENT '博客ID',
  `label_id` bigint NOT NULL COMMENT '标签id',
  KEY `blog_id` (`blog_id`),
  KEY `label_id` (`label_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='博客设置标签表';

-- ----------------------------
-- Records of tb_blog_label
-- ----------------------------
BEGIN;
COMMIT;

-- ----------------------------
-- Table structure for tb_comments
-- ----------------------------
DROP TABLE IF EXISTS `tb_comments`;
CREATE TABLE `tb_comments` (
  `comment_id` bigint NOT NULL COMMENT '评论ID',
  `user_id` bigint NOT NULL COMMENT '外键,博客拥有者ID',
  `blog_id` bigint NOT NULL COMMENT '外键,博客id',
  `comment_like_count` bigint NOT NULL DEFAULT '0' COMMENT '评论点赞数',
  `comment_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '评论日期',
  `comment_content` text NOT NULL COMMENT '评论内容',
  `parent_comment_id` bigint DEFAULT NULL COMMENT '父评论ID',
  PRIMARY KEY (`comment_id`),
  KEY `blog_id` (`blog_id`),
  KEY `comment_date` (`comment_date`),
  KEY `parent_comment_id` (`parent_comment_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='评论表';

-- ----------------------------
-- Records of tb_comments
-- ----------------------------
BEGIN;
INSERT INTO `tb_comments` (`comment_id`, `user_id`, `blog_id`, `comment_like_count`, `comment_date`, `comment_content`, `parent_comment_id`) VALUES (381083831983, 8888888888888888888, 1, 0, '2022-09-09 15:13:16', '非常棒呀', NULL);
INSERT INTO `tb_comments` (`comment_id`, `user_id`, `blog_id`, `comment_like_count`, `comment_date`, `comment_content`, `parent_comment_id`) VALUES (888999000222, 8888888888888888888, 1, 0, '2022-09-09 15:13:16', '还要继续加油呢', 381083831983);
COMMIT;

-- ----------------------------
-- Table structure for tb_labels
-- ----------------------------
DROP TABLE IF EXISTS `tb_labels`;
CREATE TABLE `tb_labels` (
  `label_id` bigint NOT NULL COMMENT '标签ID',
  `label_name` varchar(20) NOT NULL COMMENT '标签名称',
  `label_description` text NOT NULL COMMENT '标签描述',
  PRIMARY KEY (`label_id`),
  KEY `label_name` (`label_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='标签表';

-- ----------------------------
-- Records of tb_labels
-- ----------------------------
BEGIN;
COMMIT;

-- ----------------------------
-- Table structure for tb_skill
-- ----------------------------
DROP TABLE IF EXISTS `tb_skill`;
CREATE TABLE `tb_skill` (
  `id` bigint NOT NULL,
  `skill` varchar(32) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `skill` (`skill`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of tb_skill
-- ----------------------------
BEGIN;
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569671043285540866, 'AJAX');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569670854034350081, 'axios');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569667596461039617, 'HTML+CSS');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569667353816358914, 'javascript');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569560164615446530, 'javaSE');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569666745138962433, 'javaWeb');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569667932546424833, 'MyBatis');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569667955241803778, 'MyBatis-Plus');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569668068970356737, 'MySQL');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569668108623306753, 'Redis');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569666634971373570, 'spring');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569560483915132929, 'springboot');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569667735854538754, 'springMVC');
INSERT INTO `tb_skill` (`id`, `skill`) VALUES (1569666973787250689, 'Vue');
COMMIT;

-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `user_name` varchar(20) NOT NULL COMMENT '用户名',
  `user_password` varchar(255) NOT NULL COMMENT '用户密码',
  `user_email` varchar(30) NOT NULL COMMENT '用户邮箱',
  `user_avatar` varchar(255) NOT NULL COMMENT '用户头像',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间:格式2022-09-09 06:35:04',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `user_birthday` date DEFAULT NULL COMMENT '用户生日',
  `user_nickname` varchar(20) NOT NULL COMMENT '用户昵称',
  `user_signature` varchar(100) DEFAULT NULL COMMENT '用户个性签名',
  PRIMARY KEY (`user_id`),
  KEY `user_name` (`user_name`),
  KEY `user_email` (`user_email`),
  KEY `user_nickname` (`user_nickname`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';

-- ----------------------------
-- Records of tb_user
-- ----------------------------
BEGIN;
INSERT INTO `tb_user` (`user_id`, `user_name`, `user_password`, `user_email`, `user_avatar`, `create_time`, `update_time`, `user_birthday`, `user_nickname`, `user_signature`) VALUES (8888888888888888888, 'admin', 'b1c3rayemzga5xa58cts@f530361b8b475345187d567de8d44e86', '123@166.com', '../../../public/images/avatar.png', '2022-09-09 15:12:30', '2022-10-11 16:25:41', '2001-10-10', 'redyouzi', '路漫漫其修远兮 吾将上下而求索');
COMMIT;

-- ----------------------------
-- Table structure for tb_user_skill
-- ----------------------------
DROP TABLE IF EXISTS `tb_user_skill`;
CREATE TABLE `tb_user_skill` (
  `user_id` bigint NOT NULL,
  `skill_id` bigint NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- ----------------------------
-- Records of tb_user_skill
-- ----------------------------
BEGIN;
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569560164615446530);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569560483915132929);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569666634971373570);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569666745138962433);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569666973787250689);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569667353816358914);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569667596461039617);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569667735854538754);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569667932546424833);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569667955241803778);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569668068970356737);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569668108623306753);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569670854034350081);
INSERT INTO `tb_user_skill` (`user_id`, `skill_id`) VALUES (8888888888888888888, 1569671043285540866);
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;
